1. 程式人生 > 其它 >【Python環境】R vs Python:硬碰硬的資料分析

【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,fgtrb

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_datadropna方法。

繪製類別圖


我們現在可以按類別繪製球員分佈圖以發現模式。首先使用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有更多的資料分析內建函式,例如floorsampleset.seed,這些函式在Python中通過第三方庫被呼叫(math.floorrandom.samplerandom.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依賴於內建函式lmpredictpredict根據傳遞給它擬合模型的不同會表現出不同的行為,它可以被用於各種各樣的模型。

計算模型統計量


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的比分。兩個都有標題,以及每個球員和他們的比賽統計。我們現在不會將其轉換為更多的訓練資料,但是如果需要把它們加入nbadataframe,轉換可以很容易地完成。

R程式碼比Python更復雜,因為它沒有一個方便的方式使用正則表示式選擇內容,因此我們不得不做額外的處理以從HTML中得到隊伍名稱。R也不鼓勵使用for迴圈,支援沿向量應用函式。我們使用lapply做到這一點,但由於需要處理的每一行都因是否是標題而異,需要傳遞保留項的索引和整個rows列表給函式。

我們使用rvest,一個廣泛使用的新R網路抓取包實現抽取資料,注意這裡可以直接傳遞url給rvest,因此上一步在R中並不是必須的。

在Python中,我們使用了BeautifulSoup,一個最常用的web抓取包。它讓我們可以在標籤間迴圈,並以一種直接的方式構建列表的列表。

結論


我們已經看到了如何使用R和Python分析一個數據集。還有很多工沒有深入,例如儲存和分享分析結果,測試,確保生產就緒,以及構建更多的視覺化。我們會在近期繼續探討這些,從而得到更明確的結論。現在,下面是一些能夠得到的:

R更加函式化,Python更面向物件

就像我們在lmpredict和其他函式中看到的那樣,R用函式完成大部分工作。對比Python中的`LinearRegression類,還有dataframe的sample方法。

R包含更多的資料分析內建功能,Python依賴於第三方軟體包。

當我們檢視彙總統計量時,在R中可以直接使用summary內建函式,但是Python中必須依靠statsmodels包。dataframe是R內建的結構,而在Python中由pandas包引入。

Python擁有“主要的”資料分析包,R擁有由較小的包組成的更大的生態系統

在Python中,我們可以使用scikit-learn完成線性迴歸,隨機森林和許多其他任務。它提供了一致的API,並很好的維護。在R中,我們有多種多樣的包,但是也更加碎片化和不一致(線性迴歸是內建的lmrandomForest是單獨的包,等等)。

總體上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/