利用Pandas進行資料分析
原文:https://www.dataquest.io/blog/pandas-python-tutorial/
作者:Vik Paruchuri
譯者:linkcheng
Python 是進行資料分析的絕佳語言,主要原因是以資料為中心的 Python 包的奇妙生態系統。Pandas 就是其中之一,它使得匯入和分析資料更容易。
Pandas 以 NumPy 和 matplotlib 包為底層驅動,為您提供更方便的介面用來完成大多數資料分析和視覺化工作。
在這篇教程中,我們將使用 Pandas 來分析 IGN(一個熱門的視訊遊戲評論網站)的視訊遊戲評論資料。
在我們分析視訊遊戲評論的過程,將學習 Pandas 關鍵的概念,例如索引等。
像 “巫師 3” 這樣的遊戲在 PS4 上會比在 Xbox One 上獲得更好的評價嗎? 這個資料集可以幫助我們找出答案。
使用 pandas 匯入資料
我們將採取的第一步是讀取資料。資料儲存為用逗號分隔型別的檔案,如 csv 檔案,它們一行代表一條記錄,列與列之間用逗號(,
)分開
。 下面是 ign.csv
檔案的前幾行:
,score_phrase,title,url,platform,score,genre,editors_choice,release_year,release_month,release_day 0,Amazing,LittleBigPlanet PS Vita,/games/littlebigplanet-vita/vita-98907,PlayStation Vita,9.0,Platformer,Y,2012,9,12 1,Amazing,LittleBigPlanet PS Vita -- Marvel Super Hero Edition,/games/littlebigplanet-ps-vita-marvel-super-hero-edition/vita-20027059,PlayStation Vita,9.0,Platformer,Y,2012,9,12 2,Great,Splice: Tree of Life,/games/splice/ipad-141070,iPad,8.5,Puzzle,N,2012,9,12 3,Great,NHL 13,/games/nhl-13/xbox-360-128182,Xbox 360,8.5,Sports,N,2012,9,11
如上所示,資料中的每一行代表一個由 IGN 發行的遊戲。 列包含有關該遊戲的資訊:
- score_phrase - IGN 用一個詞描述該遊戲。 這與得到的分數相關聯。
- title - 遊戲的名稱。
- url - 可以在其中檢視完整評價的網址。
- platform - 遊戲評論的平臺(PC,PS4等)。
- score - 遊戲的分數,從1.0到10.0。
- genre - 遊戲的型別。
- editors_choice -
N
代表遊戲不是編輯的選擇,Y
代表是。 這與得分相關。 - release_year - 遊戲釋出的年份。
- release_month - 遊戲釋出的月份。
- release_day - 遊戲釋出的天。
還有一個包含行索引值的引導列。現在我們可以忽略此列,但隨後我們將深入索引值的意義。
為了能夠使用 Python中 的資料,我們需要將 csv 檔案讀入 Pandas DataFrame。 DataFrame (資料幀)是一種表示和處理表格資料的方法。表格資料具有行和列,就像我們的 csv 檔案一樣。
為了讀入資料,我們需要使用 pandas.read_csv 函式。 此函式將接收一個 csv 檔案並返回一個 DataFrame。
下面的程式碼將:
- 匯入
pandas
庫。 我們將它重新命名為pd
,這樣後續寫起來更方便。 - 把
ign.csv
檔案內容讀到 DataFrame 中,並將它起名字叫review
。
import pandas as pd reviews = pd.read_csv("ign.csv")
把資料讀入到 DataFrame 後,Pandas 提供兩個方法讓我們快速打印出資料。
這兩個函式是:
我們用 head
方法看下 review
中到底是些什麼:
reviews.head()
Unnamed: 0 | score_phrase | title | url | platform | score | genre | editors_choice | release_year | release_month | release_day | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | Amazing | LittleBigPlanet PS Vita | /games/littlebigplanet-vita/vita-98907 | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
1 | 1 | Amazing | LittleBigPlanet PS Vita -- Marvel Super Hero E... | /games/littlebigplanet-ps-vita-marvel-super-he... | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
2 | 2 | Great | Splice: Tree of Life | /games/splice/ipad-141070 | iPad | 8.5 | Puzzle | N | 2012 | 9 | 12 |
3 | 3 | Great | NHL 13 | /games/nhl-13/xbox-360-128182 | Xbox 360 | 8.5 | Sports | N | 2012 | 9 | 11 |
4 | 4 | Great | NHL 13 | /games/nhl-13/ps3-128181 | PlayStation 3 | 8.5 | Sports | N | 2012 | 9 | 11 |
reviews.shape
(18625,11)
如上所示,一切都已正確讀取,我們有 18625 行 11 列。
在 Pandas vs NumPy 中,Pandas 的一個很大的優點是允許你有不同資料型別的列。 reviews
具有
float 型別的列,如 score
,string 型別,如 score_phrase
和
integer 型別,如 release_year。
現在我們已經正確讀取了資料,讓我們從 reviews
中檢索我們想要的行和列。
使用 Pandas 進行 DataFrame(資料幀)檢索
之前,我們使用 head
方法列印前 5
行的 reviews
。
我們可以使用 pandas.DataFrame.iloc 方法完成同樣的事情。
iloc
方法允許我們按位置檢索行和列。 為了做到這一點,我們需要指定我們想要的行的位置,以及我們想要的列的位置。
以下程式碼與 reviews.head(5)
效果相同:
reviews.iloc[0:5,:]
Unnamed: 0 | score_phrase | title | url | platform | score | genre | editors_choice | release_year | release_month | release_day | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | Amazing | LittleBigPlanet PS Vita | /games/littlebigplanet-vita/vita-98907 | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
1 | 1 | Amazing | LittleBigPlanet PS Vita -- Marvel Super Hero E... | /games/littlebigplanet-ps-vita-marvel-super-he... | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
2 | 2 | Great | Splice: Tree of Life | /games/splice/ipad-141070 | iPad | 8.5 | Puzzle | N | 2012 | 9 | 12 |
3 | 3 | Great | NHL 13 | /games/nhl-13/xbox-360-128182 | Xbox 360 | 8.5 | Sports | N | 2012 | 9 | 11 |
4 | 4 | Great | NHL 13 | /games/nhl-13/ps3-128181 | PlayStation 3 | 8.5 | Sports | N | 2012 | 9 | 11 |
正如你看到的那樣,我們指定了我們想要的行 0:5
。 這意味著我們取 0 到
5 行的資料,但不包括第 5 行。第一行被認為是在位置 0。這時我們獲取了第 0,1,2,3
和 4
行的資料。
如果我們缺失第一個位置的值,如:5
,這個地方的預設值就是 0 。如果我們缺失最後一個位置額值,如 0
:
,它預設到 DataFrame 中的最後一行或列。
我們想要所有的列,所以我們只指定一個冒號(:),沒有任何位置。 這給了我們從0到最後一列的列。
以下是一些關於索引使用的例子,以及對結果的解釋:
-
reviews.iloc[:5,:]
– 前5
行, 及其全部列資料。 -
reviews.iloc[:,:]
– 所有行所有列的資料。 reviews.iloc[5:,5:]
– 從第5
行開始到最後一行, 及第5
列開始到最後一列的資料。reviews.iloc[:,0]
– 所有行的第一列資料。reviews.iloc[9,:]
– 第十行的所有列資料。
這種使用索引的方法與 NumPy 的索引用起來很類似。如果你想詳細瞭解,點選這裡可以獲取更多關於 Numpy 的教程。
現在我們知道了怎麼通過位置來檢索資料,那麼讓我們除去無用的第一列資訊。
reviews = reviews.iloc[:,1:] reviews.head()
score_phrase | title | url | platform | score | genre | editors_choice | release_year | release_month | release_day | |
---|---|---|---|---|---|---|---|---|---|---|
0 | Amazing | LittleBigPlanet PS Vita | /games/littlebigplanet-vita/vita-98907 | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
1 | Amazing | LittleBigPlanet PS Vita -- Marvel Super Hero E... | /games/littlebigplanet-ps-vita-marvel-super-he... | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
2 | Great | Splice: Tree of Life | /games/splice/ipad-141070 | iPad | 8.5 | Puzzle | N | 2012 | 9 | 12 |
3 | Great | NHL 13 | /games/nhl-13/xbox-360-128182 | Xbox 360 | 8.5 | Sports | N | 2012 | 9 | 11 |
4 | Great | NHL 13 | /games/nhl-13/ps3-128181 | PlayStation 3 | 8.5 | Sports | N | 2012 | 9 | 11 |
使用 Pandas 中的標籤檢索
現在我們知道如何按位置檢索行和列,而通過標籤檢索行和列是另一種值得研究的使用資料幀的方式。
Pandas 比 NumPy 的主要優點是每一列和行都有一個標籤。 儘管使用列位置也可以做一些操作,但卻很難跟蹤哪個數字對應於哪個列。
我們可以使用 pandas.DataFrame.loc 方法處理標籤,它允許我們使用標籤而不是位置進行索引。
我們可以使用 loc
方法顯示前五行 reviews
,如下所示:
reviews.loc[0:5,:]
score_phrase | title | url | platform | score | genre | editors_choice | release_year | release_month | release_day | |
---|---|---|---|---|---|---|---|---|---|---|
0 | Amazing | LittleBigPlanet PS Vita | /games/littlebigplanet-vita/vita-98907 | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
1 | Amazing | LittleBigPlanet PS Vita -- Marvel Super Hero E... | /games/littlebigplanet-ps-vita-marvel-super-he... | PlayStation Vita | 9.0 | Platformer | Y | 2012 | 9 | 12 |
2 | Great | Splice: Tree of Life | /games/splice/ipad-141070 | iPad | 8.5 | Puzzle | N | 2012 | 9 | 12 |
3 | Great | NHL 13 | /games/nhl-13/xbox-360-128182 | Xbox 360 | 8.5 | Sports | N | 2012 | 9 | 11 |
4 | Great | NHL 13 | /games/nhl-13/ps3-128181 | PlayStation 3 | 8.5 | Sports | N | 2012 | 9 | 11 |
5 | Good | Total War Battles: Shogun | /games/total-war-battles-shogun/mac-142565 | Macintosh | 7.0 | Strategy | N | 2012 | 9 | 11 |
以上結果與 reviews.iloc[0:5,:]
的結果並沒有太大不同。這是因為行標籤可以使用任意型別的值,使得行標籤是準確匹配位置。你可以在上面的表格的左側看到行標籤(加粗部分)。當然也可以通過訪問資料幀的 index 屬性,以下將會展示 reviews
的行索引屬性:
reviews.index
Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...], dtype='int64')
不過索引並不總是與位置匹配。 在下面的程式碼塊中,我們將:
- 獲取第
10
行到第20
行的reviews
,並且賦值給some_reviews
。 - 展示
some_reviews
的前5
行內容。
some_reviews = reviews.iloc[10:20,] some_reviews.head()
如上所示,some_reviews
的索引是從 10
到 20
。這樣以來,嘗試給loc
出入小於 10
或者大於 20
的值時,就會有以下錯誤:
some_reviews.loc[9:21,:]
score_phrase | title | url | platform | score | genre | editors_choice | release_year | release_month | release_day | |
---|---|---|---|---|---|---|---|---|---|---|
10 | Good | Tekken Tag Tournament 2 | /games/tekken-tag-tournament-2/ps3-124584 | PlayStation 3 | 7.5 | Fighting | N | 2012 | 9 | 11 |
11 | Good | Tekken Tag Tournament 2 | /games/tekken-tag-tournament-2/xbox-360-124581 | Xbox 360 | 7.5 | Fighting | N | 2012 | 9 | 11 |
12 | Good | Wild Blood | /games/wild-blood/iphone-139363 | iPhone | 7.0 | NaN | N | 2012 | 9 | 10 |
13 | Amazing | Mark of the Ninja | /games/mark-of-the-ninja-135615/xbox-360-129276 | Xbox 360 | 9.0 | Action, Adventure | Y | 2012 | 9 | 7 |
14 | Amazing | Mark of the Ninja | /games/mark-of-the-ninja-135615/pc-143761 | PC | 9.0 | Action, Adventure | Y | 2012 | 9 | 7 |
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-76-5378b774c9a7> in <module>() ----> 1 some_reviews.loc[9:21,:] # 省略了許多內容 /Users/vik/python_envs/dsserver/lib/python3.4/site-packages/pandas/core/indexing.py in _has_valid_type(self, key, axis) 1258 raise KeyError( 1259 "start bound [%s] is not the [%s]" % -> 1260 (key.start, self.obj._get_axis_name(axis)) 1261 ) 1262 if key.stop is not None: KeyError: 'start bound [9] is not the [index]'
譯者注:在本地執行 some_reviews.loc[9:21,:] 時並不報錯,而是以下資訊,這可能與版本有關。
正如我們前面提到的,處理資料時,使用列標籤操作起來更方便。 我們可以在 loc
方法中指定列標籤,按標籤名檢索列而不是按位置檢索列。
reviews.loc[:5, "score"]
0 9.0 1 9.0 2 8.5 3 8.5 4 8.5 5 7.0 Name: score, dtype: float64
我們也可以通過列表的方式一次列舉多列資料。
reviews.loc[:5, ["score", "release_year"]]
score | release_year | |
---|---|---|
0 | 9.0 | 2012 |
1 | 9.0 | 2012 |
2 | 8.5 | 2012 |
3 | 8.5 | 2012 |
4 | 8.5 | 2012 |
5 | 7.0 | 2012 |
Pandas Series(序列)物件
我們可以通過 Pandas 不同方法來檢索一個單個列。到目前為止,我們已經兩種型別的語法:
reviews.iloc[:,1]
- 檢索第二列reviews.loc[:," core_phrase"]
- 同樣也是第二列
還有第三種,甚至更容易的方式檢索整個列。 我們可以在方括號中指定列名,就像字典一樣:
reviews["score"]
0 9.0 1 9.0 2 8.5 3 8.5 4 8.5 5 7.0 6 3.0 7 9.0 8 3.0 9 7.0 10 7.5 11 7.5 12 7.0 13 9.0 14 9.0 15 6.5 16 6.5 17 8.0 18 5.5 19 7.0 20 7.0 21 7.5 22 7.5 23 7.5 24 9.0 25 7.0 26 9.0 27 7.5 28 8.0 29 6.5 ... 18595 4.4 18596 6.5 18597 4.9 18598 6.8 18599 7.0 18600 7.4 18601 7.4 18602 7.4 18603 7.8 18604 8.6 18605 6.0 18606 6.4 18607 7.0 18608 5.4 18609 8.0 18610 6.0 18611 5.8 18612 7.8 18613 8.0 18614 9.2 18615 9.2 18616 7.5 18617 8.4 18618 9.1 18619 7.9 18620 7.6 18621 9.0 18622 5.8 18623 10.0 18624 10.0 Name: score, dtype: float64
我們依然可以使用列表的方式:
reviews[["score", "release_year"]]
score | release_year | |
---|---|---|
0 | 9.0 | 2012 |
1 | 9.0 | 2012 |
2 | 8.5 | 2012 |
3 | 8.5 | 2012 |
4 | 8.5 | 2012 |
5 | 7.0 | 2012 |
6 | 3.0 | 2012 |
7 | 9.0 | 2012 |
8 | 3.0 | 2012 |
9 | 7.0 | 2012 |
10 | 7.5 | 2012 |
11 | 7.5 | 2012 |
12 | 7.0 | 2012 |
13 | 9.0 | 2012 |
14 | 9.0 | 2012 |
15 | 6.5 | 2012 |
16 | 6.5 | 2012 |
17 | 8.0 | 2012 |
18 | 5.5 | 2012 |
19 | 7.0 | 2012 |
20 | 7.0 | 2012 |
21 | 7.5 | 2012 |
22 | 7.5 | 2012 |
23 | 7.5 | 2012 |
24 | 9.0 | 2012 |
25 | 7.0 | 2012 |
26 | 9.0 | 2012 |
27 | 7.5 | 2012 |
28 | 8.0 | 2012 |
29 | 6.5 | 2012 |
... | ... | ... |
18595 | 4.4 | 2016 |
18596 | 6.5 | 2016 |
18597 | 4.9 | 2016 |
18598 | 6.8 | 2016 |
18599 | 7.0 | 2016 |
18600 | 7.4 | 2016 |
18601 | 7.4 | 2016 |
18602 | 7.4 | 2016 |
18603 | 7.8 | 2016 |
18604 | 8.6 | 2016 |
18605 | 6.0 | 2016 |
18606 | 6.4 | 2016 |
18607 | 7.0 | 2016 |
18608 | 5.4 | 2016 |
18609 | 8.0 | 2016 |
18610 | 6.0 | 2016 |
18611 | 5.8 | 2016 |
18612 | 7.8 | 2016 |
18613 | 8.0 | 2016 |
18614 | 9.2 | 2016 |
18615 | 9.2 | 2016 |
18616 | 7.5 | 2016 |
18617 | 8.4 | 2016 |
18618 | 9.1 | 2016 |
18619 | 7.9 | 2016 |
18620 | 7.6 | 2016 |
18621 | 9.0 | 2016 |
18622 | 5.8 | 2016 |
18623 | 10.0 | 2016 |
18624 | 10.0 | 2016 |
18625 rows × 2 columns
當我們檢索單個列時,我們實際上檢索了一個 Pandas Series 物件。 DataFrame 儲存表格資料,但是 Series 儲存單個數據列或行。
我們可以驗證單個列是否為系列:
type(reviews["score"])
pandas.core.series.Series
我們可以手動建立一個 Series,以更好地瞭解它是如何工作的。 建立一個 Series,當我們例項化它時,我們需要將一個 list 型別或 NumPy 陣列傳遞給 Series 物件:
s1 = pd.Series([1, 2]) s1
0 1 1 2 dtype: int64
Serise 可以包含任何型別的資料,包括混合型別。 在這裡,我們建立一個包含字串物件的 Serise:
s2 = pd.Series(["Boris Yeltsin", "Mikhail Gorbachev"]) s2
0 Boris Yeltsin 1 Mikhail Gorbachev dtype: object
通過 Pandas 建立 資料幀 DataFrame
我們可以通過將多個 Series 物件傳遞給 DataFrame 類來建立一個 DataFrame。 在這裡,我們傳入我們剛剛建立的兩個 Series 物件,s1
作為第一行,s2
作為第二行:
pd.DataFrame([s1, s2])
0 | 1 | |
---|---|---|
0 | 1 | 2 |
1 | Boris Yeltsin | Mikhail Gorbachev |
我們也可以通過傳遞成員為列表的列表來完成同樣的事情。 每個內部列表在結果 DataFrame 中被視為一行:
pd.DataFrame( [ [1,2], ["Boris Yeltsin", "Mikhail Gorbachev"] ] )
0 | 1 | |
---|---|---|
0 | 1 | 2 |
1 | Boris Yeltsin | Mikhail Gorbachev |
我們可以在建立 DataFrame 時指定列標籤:
pd.DataFrame( [ [1,2], ["Boris Yeltsin", "Mikhail Gorbachev"] ], columns=["column1", "column2"] )
column1 | column2 | |
---|---|---|
0 | 1 | 2 |
1 | Boris Yeltsin | Mikhail Gorbachev |
同樣可以指定行標籤(即索引 index ):
frame = pd.DataFrame( [ [1, 2], ["Boris Yeltsin", "Mikhail Gorbachev"] ], index=["row1", "row2"], columns=["column1", "column2"] ) frame
column1 | column2 | |
---|---|---|
row1 | 1 | 2 |
row2 | Boris Yeltsin | Mikhail Gorbachev |
然後,我們可以使用以下標籤對DataFrame建立索引:
frame.loc["row1":"row2", "column1"]
row1 1 row2 Boris Yeltsin Name: column1, dtype: object
如果我們將字典傳遞給 DataFrame 的建構函式,那麼我們可以跳過指定 column
關鍵字引數。
這將會自動為列設定名稱:
frame = pd.DataFrame( { "column1": [1, 2], "column2": ["Boris Yeltsin", "Mikhail Gorbachev"] } ) frame
column1 | column2 | |
---|---|---|
0 | 1 | Boris Yeltsin |
1 | 2 | Mikhail Gorbachev |
Pandas DataFrame 方法
正如之前我們提到的,DataFrame 的每一個列都是一個 Series 物件
type(reviews["title"])
pandas.core.series.Series
那些對 DataFrame 適用的方法大多同樣也適用於 Series 物件,包括 head
:
reviews["title"].head()
0 LittleBigPlanet PS Vita 1 LittleBigPlanet PS Vita -- Marvel Super Hero E... 2 Splice: Tree of Life 3 NHL 13 4 NHL 13 Name: title, dtype: object
Pandas Series 和 DataFrames 中也有其他方法,使計算更簡單。 例如,我們可以使用 pandas.Series.mean 方法來查詢一個 Series 的平均值:
reviews['score'].mean()
6.950459060402685
我們也可以呼叫類似的 pandas.DataFrame.mean 方法,它將在預設情況下找到 DataFrame 中每個數字列的平均值:
reviews.mean()
score 6.950459 release_year 2006.515329 release_month 7.138470 release_day 15.603866 dtype: float64
我們可以通過給 mean
方法新增 axis
關鍵字引數,用來計算每行或者每列的平均值。axis
的預設值為
0 ,並計算每一列的平均值。也可以設定為 1 來計算每一行的平均值。請注意,這隻會計算每一行中型別為數值的平均值:
reviews.mean(axis=1)
0 510.500 1 510.500 2 510.375 3 510.125 4 510.125 5 509.750 6 508.750 7 510.250 8 508.750 9 509.750 10 509.875 11 509.875 12 509.500 13 509.250 14 509.250 15 508.375 16 508.375 17 508.500 18 507.375 19 507.750 20 507.750 21 514.625 22 514.625 23 514.625 24 515.000 25 514.250 26 514.750 27 514.125 28 514.250 29 513.625 ... 18595 510.850 18596 510.875 18597 510.225 18598 510.700 18599 510.750 18600 512.600 18601 512.600 18602 512.600 18603 512.450 18604 512.400 18605 511.500 18606 508.600 18607 510.750 18608 510.350 18609 510.750 18610 510.250 18611 508.700 18612 509.200 18613 508.000 18614 515.050 18615 515.050 18616 508.375 18617 508.600 18618 515.025 18619 514.725 18620 514.650 18621 515.000 18622 513.950 18623 515.000 18624 515.000 dtype: float64
在 Series 和 DataFrames 上有很多方法,它們的行為類似於 mean
。
這裡有一些常見的:
我們可以使用 corr
方法來檢視是否有任何列與 score 相關。 例如,通過這種方式我們可以得到,最近釋出的遊戲是否獲得了更高的評價(release_year),或者是否年底釋出的遊戲得分更好(release_month):
reviews.corr()
score | release_yea |
---|