【Python環境】R vs Python:硬碰硬的資料分析
我們將在已有的數十篇從主觀角度對比Python和R的文章中加入自己的觀點,但是這篇文章旨在更客觀地看待這兩門語言。我們會平行使用Python和R分析一個數據集,展示兩種語言在實現相同結果時需要使用什麼樣的程式碼。這讓我們瞭解每種語言的優缺點,而不是猜想。在Dataquest,我們教授兩種語言,並認為兩者在資料科學工具箱中都佔據各自的地位。
我們將會分析一個NBA資料集,包含運動員和他們在2013-2014賽季的表現,可以在這裡下載這個資料集。我們展示Python和R的程式碼,同時做出一些解釋和討論。事不宜遲,現在就開始這場硬碰硬的對決吧!
讀取CSV檔案
R
nba <- read.csv("nba_2013.csv")
Python
import pandas
nba = pandas.read_csv("nba_2013.csv")
上面的程式碼分別在兩種語言中將包含2013-2014賽季NBA球員的資料的 nba_2013.csv
檔案載入為變數nba
。Python中實際的唯一不同是需要載入pandas庫以使用Dataframe。Dataframe在R和Python中都可用,它是一個二維陣列(矩陣),其中每列都可以是不同的資料型別。在完成這一步後,csv檔案在兩種語言中都載入為dataframe。
統計球員數量
R
print(dim(nba))
[1] 481 31
Python
print(nba.shape)
(481, 31)
兩者分別輸出球員數量和資料列數量。我們有481行,或者說球員,和31列關於球員的資料。
檢視資料的第一行
R
print(head(nba, 1))
player pos age bref_team_id
1 Quincy Acy SF 23 TOT
[output truncated]
Python
print(nba.head(1))
player pos age bref_team_id 0 Quincy Acy SF 23 TOT [output truncated]
它們幾乎完全相同。兩種語言都打印出資料的第一行,語法也非常類似。Python在這裡更面向物件一些,head
是dataframe物件的一個方法,而R具有一個單獨的head
函式。當開始使用這些語言做分析時,這是一個共同的主題,可以看到Python更加面向物件而R更函式化。
計算每個指標的均值
讓我們為每個指標計算均值。如你所見,資料列以類似fg
(field goals made)和ast
(assists)的名稱命名。它們都是球員的賽季統計指標。如果想得到指標的完整說明,參閱這裡。
R
meanNoNA <- function(values){
mean(values, na.rm=TRUE)
}
sapply(nba, meanNoNA)
player NA
pos NAage 26.5093555093555
bref_team_id NA
[output truncated]
Python
import numpy
nba_numeric = nba._get_numeric_data()
nba_numeric.apply(numpy,.mean, axis=0)
age 26.509356
g 53.253638
gs 25.571726
[output truncated]
這裡有一些明顯的分歧。在兩種方法中,我們均在dataframe的列上應用了一個函式。在python中,如果我們在非數值列(例如球員姓名)上應用函式,會返回一個錯誤。要避免這種情況,我們只有在取平均值之前選擇數值列。
在R中,對字串列求均值會得到NA
——not available(不可用)。然而,我們在取均值時需要確實忽略NA
(因此需要構建我們自己的函式)。否則類似x3p.
這樣的一些列的均值將會為NA
,這一列代表三分球的比例。有些球員沒有投出三分球,他們的百分比就是缺失的。如果我們直接使用R中的mean
函式,就會得到NA
,除非我們指定na.rm=TRUE
,在計算均值時忽略缺失值。
繪製成對散點圖
一個探索資料的常用方法是檢視列與列之間有多相關。我們將會比較ast
,fg
和trb
。
R
library(GGally)
ggpairs(nba[, c("ast", "fg", "trb")])
import seaborn as snsimport matplotlib.pyplot as plt
sns.pairplot(nba[["ast", "fg", "trb"]])
plt.show()
我們會得到非常相似的兩張圖,但是可以看到R的資料科學生態中有許多較小的軟體包(GGally是最常用的R繪圖包ggplot2的輔助包)和更多的通用視覺化軟體包。在Python中,matplotlib是主要的繪圖包,seaborn是一個廣泛用於matplotlib上的圖層。Python中的視覺化通常只有一種蛀牙哦的方法完成某件事,而R中可能有許多包支援不同的方法(例如,至少有半打繪製成對散點圖的包)。
對球員聚類
另一個很好探索資料的方式是生成類別圖。這將會顯示哪些球員更相似。
R
library(cluster)
set.seed(1)
isGoodCol <- function(col){
sum(is.na(col)) == 0 && is.numeric(col)
}
goodCols <- sapply(nba, isGoodCol)
clusters <- kmeans(nba[,goodCols], centers=5)
labels <- clusters$cluster
Python
from sklearn.cluster import KMeans
kmeans_model = KMeans(n_clusters=5, random_state=1)
good_columns = nba._get_numeric_data().dropna(axis=1)
kmeans_model.fit(good_columns)
labels = kmeans_model.labels_
為了正確的聚類,我們移除了所有非數值列,以及包含缺失值的列。在R中,我們在每一列上應用一個函式,如果該列包含任何缺失值或不是數值,則刪除它。接下來我們使用cluster包實施k-means聚類,在資料中發現5個簇。通過set.seed
設定隨機種子以使結果可復現。
在Python中,我們使用了主要的Python機器學習包scikit-learn擬合k-means模型並得到類別標籤。資料準備的過程和R非常類似,但是用到了get_numeric_data
和dropna
方法。
繪製類別圖
我們現在可以按類別繪製球員分佈圖以發現模式。首先使用PCA將資料降至2維,然後畫圖,用不同標記或深淺的點標誌類別。
nba2d <- prcomp(nba[,goodCols], center=TRUE)
twoColumns <- nba2d$x[,1:2]
clusplot(twoColumns, labels)
Python
from sklearn.decomposition import PCA
pca_2 = PCA(2)
plot_columns = pca_2.fit_transform(good_columns)
plt.scatter(x=plot_columns[:,0], y=plot_columns[:,1], c=labels)
plt.show()
在R中,我們通過聚類庫中的函式clusplot
函式繪圖,使用內建函式pccomp
實行PCA。
在Python中,我們使用scikit-learn庫中的PCA類,使用matplotlib建立圖形。
劃分訓練集和測試集
如果我們希望進行監督性機器學習,將資料劃分為訓練集和測試集是一個避免過擬合的好辦法。
R
trainRowCount <- floor(0.8 * nrow(nba))
set.seed(1)
trainIndex <- sample(1:nrow(nba), trainRowCount)
train <- nba[trainIndex,]
test <- nba[-trainIndex,]
Python
train = nba.sample(frac=0.8, random_state=1)
test = nba.loc[~nba.index.isin(train.index)]
你能注意到R有更多的資料分析內建函式,例如floor
,sample
和set.seed
,這些函式在Python中通過第三方庫被呼叫(math.floor
,random.sample
,random.seed
)。在Python中,最新版本的pandas包含一個sample
方法,返回對原始dataframe確定比例的隨機抽樣,這使得程式碼更加簡潔。在R中,有很多包可以使抽樣更容易,但是沒有一個比使用內建sample
函式更簡潔。在兩個例子中,我們都設定了隨機種子以保證結果的可重複性。
一元線性迴歸
假設我們希望通過球員的得分預測其助攻次數。
R
fit <- lm(ast ~ fg, data=train)
predictions <- predict(fit, test)
Python
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train[["fg"]], train["ast"])
predictions = lr.predict(test[["fg"]])
Scikit-learn包含一個線性迴歸模型,我們可以通過它擬合併生成預測。R依賴於內建函式lm
和predict
。predict
根據傳遞給它擬合模型的不同會表現出不同的行為,它可以被用於各種各樣的模型。
計算模型統計量
R
summary(fit)
Call:
lm(formula = ast ~ fg, data = train)
Residuals:
Min 1Q Median 3Q Max
-228.26 -35.38 -11.45 11.99 559.61
[output truncated]
Python
import statsmodels.formula.api as sm
model = sm.ols(formula='ast ~ fga', data=train)
fitted = model.fit()
print(fitted.summary())
OLS Regression Results
============================
Dep. Variable: ast
R-squared: 0.568
Model: OLS
Adj. R-squared: 0.567
[output truncated]
如果希望得到類似R平方值這樣的模型統計量,在Python中需要比R多做一點。在R中,我們可以使用內建summary
函式得到模型資訊。在Python中,我們需要使用statsmodels包,這個包包含許多統計模型的Python實現。我們得到類似的結果,總體來說在Python中進行統計分析稍有點困難,一些R中存在的統計方法也沒有存在於Python。
擬合一個隨機森林模型
一元線性迴歸表現的不錯,但是我們懷疑資料中可能存在非線性。因此,我們想要擬合一個隨機森林模型。
R
library(randomForest)
predictorColumns <- c("age", "mp", "fg", "trb", "stl", "blk")
rf <- randomForest(train[predictorColumns], train$ast, ntree=100)
predictions <- predict(rf, test[predictorColumns])
Python
from sklearn.ensemble import RandomForestRegressor
predictor_columns = ["age", "mp", "fg", "trb", "stl", "blk"]
rf = RandomForestRegressor(n_estimators=100, min_samples_leaf=3)
rf.fit(train[predictor_columns], train["ast"])
predictions = rf.predict(test[predictor_columns])
這裡主要的區別是R需要使用randomForest庫實現演算法,而Python中的scikit-learn直接內建其中。scikit-learn為許多不同的機器學習演算法提供了統一的互動介面,在Python中每種演算法通常只有一個主要的實現。而R中有許多包含單個演算法較小的包,一般訪問的方法並不一致。這導致演算法更加的多樣化(很多演算法有多個實現,還有那些新問世的演算法),但是隻有一小部分是可用的。
計算誤差
現在已經擬合了兩個模型,下面讓我們計算誤差,使用MSE
R
mean((test["ast"] - predictions)^2)
4573.86778567462
Python
from sklearn.metrics import mean_squared_error
mean_squared_error(test["ast"], predictions)
4166.9202475632374
Python中的scikit-learn庫包含我們可以使用的各種誤差量度。在R中,可能有一些小的第三方庫計算MSE,但是兩種語言中手動計算它都很容易。誤差的細微差異幾乎可以肯定是由於引數調整造成的,並沒什麼關係。
下載一個網頁
現在已經有了2013-2014賽季的NBA球員資料,讓我們抓取一些額外資料補充它。為了節省時間,在這裡看一場NBA總決賽的比分。
R
library(RCurl)
url <- "http://www.basketball-reference.com/boxscores/201506140GSW.html"page <- getURL(url)
tc <- textConnection(page)
data <- readLines(tc)
close(tc)
Python
import requests
url = "http://www.basketball-reference.com/boxscores/201506140GSW.html"data = requests.get(url).content
Python中的requests包為所有的請求型別使用統一的API介面,下載網頁非常容易。在R中,RCurl提供稍微複雜方法發起請求。兩者都把網頁下載為字串型別的資料。注:這在R中的下一步並不是必須,只是為了比較的原因。
抽取球員比分
現在我們已經下載了網頁,需要處理它以抽取球員比分。
R
library(rvest)
page <- read_html(url)
table <- html_nodes(page, ".stats_table")[3]
rows <- html_nodes(table, "tr")
cells <- html_nodes(rows, "td a")
teams <- html_text(cells)
extractRow <- function(rows, i){
if(i == 1){
return
}
row <- rows[i]
tag <- "td"
if(i == 2){
tag <- "th"
}
items <- html_nodes(row, tag)
html_text(items)
}
scrapeData <- function(team){
teamData <- html_nodes(page, paste("#",team,"_basic", sep=""))
rows <- html_nodes(teamData, "tr")
lapply(seq_along(rows), extractRow, rows=rows)
}
data <- lapply(teams, scrapeData)
Python
from bs4 import BeautifulSoupimport re
soup = BeautifulSoup(data, 'html.parser')
box_scores = []for tag in soup.find_all(id=re.compile("[A-Z]{3,}_basic")):
rows = [] for i, row in enumerate(tag.find_all("tr")):
if i == 0:
continue
elif i == 1:
tag = "th"
else:
tag = "td"
row_data = [item.get_text() for item in row.find_all(tag)]
rows.append(row_data)
box_scores.append(rows)
這將建立一個包含兩個列表的列表,第一個是CLE
的比分,第二個是GSW
的比分。兩個都有標題,以及每個球員和他們的比賽統計。我們現在不會將其轉換為更多的訓練資料,但是如果需要把它們加入nba
dataframe,轉換可以很容易地完成。
R程式碼比Python更復雜,因為它沒有一個方便的方式使用正則表示式選擇內容,因此我們不得不做額外的處理以從HTML中得到隊伍名稱。R也不鼓勵使用for
迴圈,支援沿向量應用函式。我們使用lapply
做到這一點,但由於需要處理的每一行都因是否是標題而異,需要傳遞保留項的索引和整個rows
列表給函式。
我們使用rvest
,一個廣泛使用的新R網路抓取包實現抽取資料,注意這裡可以直接傳遞url給rvest,因此上一步在R中並不是必須的。
在Python中,我們使用了BeautifulSoup,一個最常用的web抓取包。它讓我們可以在標籤間迴圈,並以一種直接的方式構建列表的列表。
結論
我們已經看到了如何使用R和Python分析一個數據集。還有很多工沒有深入,例如儲存和分享分析結果,測試,確保生產就緒,以及構建更多的視覺化。我們會在近期繼續探討這些,從而得到更明確的結論。現在,下面是一些能夠得到的:
R更加函式化,Python更面向物件
就像我們在lm
,predict
和其他函式中看到的那樣,R用函式完成大部分工作。對比Python中的`LinearRegression類,還有dataframe的sample方法。
R包含更多的資料分析內建功能,Python依賴於第三方軟體包。
當我們檢視彙總統計量時,在R中可以直接使用summary
內建函式,但是Python中必須依靠statsmodels包。dataframe是R內建的結構,而在Python中由pandas
包引入。
Python擁有“主要的”資料分析包,R擁有由較小的包組成的更大的生態系統
在Python中,我們可以使用scikit-learn完成線性迴歸,隨機森林和許多其他任務。它提供了一致的API,並很好的維護。在R中,我們有多種多樣的包,但是也更加碎片化和不一致(線性迴歸是內建的lm
,randomForest
是單獨的包,等等)。
總體上R有更多的統計支援
R是作為統計語言被構建的,它也顯示了這一點。Python中的statsmodels
和其他軟體包提供了統計方法的大部分實現,但是R的生態系統要大的多。
Python中完成非統計任務通常更加直接
有了類似BeautifulSoup和request這樣良好維護的軟體包,Python中的網頁抓取遠易於R。這種說法也適於我們還未關注的其他任務,例如儲存資料庫,部署web伺服器或運行復雜的工作流。
資料分析工作流在兩者之間有許多相似之處
R和Python之間有一些互相啟發的地方(pandas的Dataframe受到R中dataframe的影響,rvest包來自BeautifulSoup的啟發),兩者的生態系統都在不斷髮展壯大,對兩種語言中許多共同的任務來說,語法和實現都是非常相似的。
總結
在Dataquest,我們首先教授Python,但是最近也加入了R的課程。我們看到這兩種語言是互補的,雖然Python在更多領域更強大,但R是一種高效的語言。它可以作為Python在資料探索和統計等領域的補充,或者你惟一的資料分析工具。正如本篇文章中所顯示的,兩種語言有許多相似的語法和實現方法,你不能在一個或另一個,或者兩者中出錯。
原文連結:https://www.dataquest.io/blog/python-vs-r/