by 軒轅御龍
數據分析之 pandas 初步
pandas
是一個常用的第三方 Python 庫,提供快速靈活的數據處理功能,也是進行數據分析的有力工具。我們的口號是:“更快,更高,更強”(皮一下)。啊,當然,現在經常有很多庫一上來就要“吊打”pandas
,咱們還是不必在意。
pandas
尤其擅長處理以下數據:
以下幾種數據尤其適合用pandas
進行處理:
- 多種數據混合的扁平化數據格式,比如 SQL 表和 Excel 電子表格;
- 時間序列數據,不管有序無序;
- 任意帶有行列標籤的矩陣數據,不管是同種數據類型還是多種數據類型;
- 還有其他任意的統計數據集,不必帶標籤。
在本文開頭要提醒的是,pandas
指的不是英語中的“panda”,熊貓。實際上pandas
是術語“panel data”(面板數據)的簡寫,大家要注意:這不是國寶模塊哈哈,不要看到這個模塊就想起憨憨的大熊貓啦~
1. pandas 安裝
有了之前文章的鋪墊,其實安裝這個步驟大家應該已經很熟悉了,但是在這裏依然要再敘述一遍。已經安裝好的同學跳過就好。
由於pandas
並非 Python 的內置模塊,因此我們直接從 Python 官網下載安裝的發行版是不包含 pandas
這個模塊的。這個時候你要是想import numpy
,顯然是會無功而返的。因此我們需要額外安裝 pandas
模塊。
安裝 pandas
有好幾種方式,我們這裏推薦的是:1)使用pip
進行安裝;2)安裝Anaconda。
1.1 使用pip
安裝
這種方式推薦給已經從 Python 官網下載了某個 Python 發行版的讀者,或是已經通過其它方式獲得了 Python 環境,但卻沒有 pandas
這個模塊的讀者。
安裝命令:
pip install pandas
或:
python -m pip install pandas
均可。
當然,實際上 pandas
模塊本身也有很多依賴,也需要其他一些模塊才能夠真正發揮出它強大的功能,因此我們推薦一次安裝多個模塊:
python -m pip install --user numpy scipy matplotlib ipython jupyter pandas sympy nose
1.2 安裝 Anaconda
這種方式適合還沒有安裝 Python 的讀者,或是已經安裝了 Python 但是想一勞永逸擁有大多數科學計算庫的讀者。
訪問 Anaconda 官網找到下載鏈接進行安裝即可。
或者如果你覺得 Anaconda 過於臃腫,也可以安裝其簡化版本 Miniconda 。
2. 主要數據類型
pandas
中有兩個主要的數據類型,一種是 Series,稱爲“序列”,是一種一維數據結構;另一種是 DataFrame,稱爲“數據幀”或是“數據框架”,是一種二維數據結構。在pandas
中,我們就是使用這兩種主要的數據結構,“喜迎四海賓朋,笑對八方來客”,分分鐘處理掉天上地下來的各種數據。
其中,Series 內部要求是同種數據,而 DataFrame 則可以使混合數據。更進一步地說, DataFrame 其實就是包含了一至多個 Series。
前面我們學習了numpy
模塊的基本相關知識,如果大家還有印象的話可以回憶一下。
numpy
雖然提供了強大的多維數組供我們進行數據處理,但是這中間有一個要命的問題:維數比較少的時候還好,維數一旦多起來,你還分得清哪個軸代表什麼意義嗎?呃當然不排除有人可以,但是大多數人肯定是不行的,因爲numpy
的每個軸之間其實沒有什麼本質上的差異,你可以是軸 1,我也可以是軸 1,誰有比誰高貴怎麼滴?因此在使用numpy
進行高維數據處理,尤其是當其中每個維度都有特定的意義時,使用numpy
的多維數組就會給使用者造成很大的負擔——而這些本來不應該是由使用者負擔的。
因此pandas
的優勢就體現出來了。pandas
可以爲每一列數據打上標籤,這樣通過標籤就可以直接區分開每個軸誰是誰,也可以通過標籤獲得更具語義性的信息,知道每列數據都是什麼、有什麼用途。即使是交換了順序也無所謂,比較在pandas
中,我們可以不再以默認的序號作爲索引。
當然pandas
還有可以組合多種數據類型等優勢,這些就留待大家在實踐中體會啦~
我們預先導入pandas
,並且由於演示過程中會用到numpy
模塊,在這裏也一併導入:
import numpy as np
import pandas as pd
2.1 Series
Series 實際上是一個帶標籤的一維數組,數組中的內容可以是任何數據類型。在 Series 中,“帶標籤的軸”統稱爲“index(索引)”,類似於我們之前學習的字典數據類型中的“key(關鍵字)”。
2.1.1 創建 Series
創建 Series 最常用的方法就是調用pd.Series
:
>>> s = pd.Series(data, index=index)
其中,data
要求是下列數據類型之一:
- Python 字典;
- Python 列表;
- N 維數組;
- 標量(即一個數字)。
而參數index
則應當是一個用來指定軸標籤的列表。
按照原始數據類型的不同,創建 Series 的方式也分爲 4 種:1)用 Python 字典創建;2)用 Python 列表創建;3)用 N 維數組創建;4)用標量創建。
- Python 字典
>>> d = {'b': 1, 'a': 0, 'c': 2}
>>> s = pd.Series(d)
>>> s
b 1
a 0
c 2
dtype: int64
- Python 列表
>>> l = [1,2,3]
>>> s = pd.Series(l)
>>> s
0 1
1 2
2 3
dtype: int64
可以看到,在只使用列表而不提供索引值時,pandas
會自動爲 Series 中的數據分配默認索引作爲標籤。
- N 維數組
>>> ar = np.random.randn(5)
>>> ar
array([-0.12383463, 0.2312694 , 1.82605315, -1.4743252 , -0.71267657])
>>> s = pd.Series(ar, index=['a', 'b', 'c', 'd', 'e'])
>>> s
a -0.123835
b 0.231269
c 1.826053
d -1.474325
e -0.712677
dtype: float64
- 標量
>>> # 標量生成單元素序列
>>> s = pd.Series(5.)
>>> s
0 5.0
dtype: float64
>>>
>>> # 標量生成多元素序列
>>> s = pd.Series(5., index=['a', 'b', 'c', 'd', 'e'])
>>> s
a 5.0
b 5.0
c 5.0
d 5.0
e 5.0
dtype: float64
2.1.2 索引 Series
既然說到 Series 類似於一維數組,也就是說 Series 也可以通過序號進行索引:
>>> s = pd.Series([5,-4,7,-8,9], index=['a','b','c','d','e'])
>>> s
a 5
b -4
c 7
d -8
e 9
dtype: int64
>>> s[2]
7
>>> s[1]
-4
其次,Series 還可以通過標籤進行索引:
>>> s['a']
5
>>> s['d']
-8
並且 Series 也有切片功能:
>>> s[1:3]
b -4
c 7
dtype: int64
>>> s[:3]
a 5
b -4
c 7
dtype: int64
甚至標籤也可以用於切片:
>>> s['a':'d']
a 5
b -4
c 7
d -8
dtype: int64
都是觀察可以發現,使用標籤進行索引與使用序號進行索引還是存在一點兒不同:使用序號進行索引時,切片結果不會包括結束序號對應的內容;但使用標籤進行索引就會包括末尾標籤指定的內容。
還可以從 Series 中直接選取特定的項。同樣地,也是既可以使用序號,也可以使用標籤,但要記得將指定的序號或標籤放在一個列表中:
>>> # 使用序號
>>> s[[1,2]]
b -4
c 7
dtype: int64
>>>
>>> # 使用標籤
>>> s[['a','d']]
a 5
d -8
dtype: int64
此外還有根據條件進行篩選的用法,這種用法是一種pandas
的if-then
方言:
>>> s[s > 0]
a 5
c 7
e 9
dtype: int64
這樣就提取出了s
中大於 0 的部分。
還要注意,Series 的直接賦值不會創建副本,如果想要新的 Series 對象與舊的沒有關係,需要顯式地創建副本並賦值:
>>> s2 = s
>>> s2 is s
True
>>> s2 = s.copy()
>>> s2 is s
False
2.1.3 Series 的運算
類似numpy
數組地,Series 的算術運算——包括應用於很多numpy
中的函數——也是逐元素進行的:
>>> s + s
a 10.0
b -8.0
c 14.0
d -16.0
e 18.0
dtype: float64
>>> s * 2
a 10.0
b -8.0
c 14.0
d -16.0
e 18.0
dtype: float64
>>> np.exp(s)
a 148.413159
b 0.018316
c 1096.633158
d 0.000335
e 8103.083928
dtype: float64
而 Series 與numpy
數組的不同之處在於,Series 有自動對齊的特性,也就是說,在運算中如果兩個參與運算的 Series 數據長度不一樣,pandas
會自動用默認的缺省值補全缺失的部分,以使運算順利進行:
>>> s[1:] + s[:-1]
a NaN
b -8.0
c 14.0
d -16.0
e NaN
dtype: float64
簡單來講,在運算時如果遇到了“在某個運算對象中找不到對應項標籤”的情況,那麼pandas
就會自作主張用缺省值NaN
來代替。這就使得在進行交互式數據分析的時候有了極大的靈活性。
2.2 DataFrame
2.2.1 創建 DataFrame
DataFrame 是一種二維帶標籤的數據結構,並且允許各列直接數據類型不同。我們既可以把它當做是電子表格或是 SQL 表,也可以將其當作是一個由若干個 Series 對象組成的字典;也是pandas
中最常用的數據結構。
創建 DataFrame 的方法是調用pd.DataFrame
:
pd.Series(data, index=index, columns=columns)
其中的data
參數要求是下列數據類型之一:
- 由一維數組、列表、字典或是 Series 構成的字典;
- 二維 Numpy 數組;
- 結構化數組;
- 一個 Series;
- 別的 DataFrame。
參數index
對應於 DataFrame 中的行標籤,參數columns
對應於DataFrame 中的列標籤。通過指定這兩個參數,可以有篩選使用哪些數據來生成 DataFrame。
在這裏我們僅僅介紹 3 中可能用到的方法。
- 使用由 Series 組成的字典或由字典組成的的字典來創建 DataFrame
如果不指定
columns
參數的話,默認將columns
設置爲字典關鍵字的有序列表。
>>> d = {'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
... 'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
>>> df = pd.DataFrame(d, index=['d', 'b', 'a'])
>>> df
one two
d NaN 4.0
b 2.0 2.0
a 1.0 1.0
>>> df = pd.DataFrame(d, index=['d', 'b', 'a'], columns=['two', 'three'])
>>> df
two three
d 4.0 NaN
b 2.0 NaN
a 1.0 NaN
>>>
>>> # 默認形式
>>> df = pd.DataFrame(d)
>>> df
one two
a 1.0 1.0
b 2.0 2.0
c 3.0 3.0
d NaN 4.0
- 使用 N 維數組或列表的字典
>>> d = {'one': [1., 2., 3., 4.],
... 'two': [4., 3., 2., 1.]}
>>> pd.DataFrame(d)
one two
0 1.0 4.0
1 2.0 3.0
2 3.0 2.0
3 4.0 1.0
>>> pd.DataFrame(d, index=['a', 'b', 'c', 'd'])
one two
a 1.0 4.0
b 2.0 3.0
c 3.0 2.0
d 4.0 1.0
- 使用字典組成的列表
>>> data2 = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]
>>> pd.DataFrame(data2)
a b c
0 1 2 NaN
1 5 10 20.0
>>> pd.DataFrame(data2, index=['first', 'second'])
a b c
first 1 2 NaN
second 5 10 20.0
>>> pd.DataFrame(data2, columns=['a', 'b'])
a b
0 1 2
1 5 10
2.2.2 索引 DataFrame
通過index
和columns
兩個屬性可以分別查看 DataFrame 的行標籤和列標籤:
>>> d = {'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
... 'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
>>> df = pd.DataFrame(d)
>>> df
one two
a 1.0 1.0
b 2.0 2.0
c 3.0 3.0
d NaN 4.0
>>> df.index
Index(['a', 'b', 'c', 'd'], dtype='object')
>>> df.columns
Index(['one', 'two'], dtype='object')
可以使用列標籤來索引:
>>> df['one']
a 1.0
b 2.0
c 3.0
d NaN
Name: one, dtype: float64
也可以直接將列標籤作爲屬性:
>>> df.one
a 1.0
b 2.0
c 3.0
d NaN
Name: one, dtype: float64
還可以按列選取:
>>> df[['one', 'two']]
one two
a 1.0 1.0
b 2.0 2.0
c 3.0 3.0
d NaN 4.0
要對 DataFrame 按行索引,則需要使用loc
這個屬性:
>>> df.loc['a']
one 1.0
two 1.0
Name: a, dtype: float64
同樣地,也可以通過行標籤來按行切片、選取:
>>> df.loc['a':'c']
one two
a 1.0 1.0
b 2.0 2.0
c 3.0 3.0
>>> df.loc[['a','d']]
one two
a 1.0 1.0
d NaN 4.0
此外,還可以使用head
和tail
來分別獲取數據的前、後幾行,具體數目由參數指定:
>>> df.head(2)
one two
a 1.0 1.0
b 2.0 2.0
>>> df.tail(2)
one two
c 3.0 3.0
d NaN 4.0
2.2.3 統計信息
使用describe
可以計算得到一個 DataFrame 數據的相關統計信息,並且計算統計信息時會自動忽略缺省值NaN
。
>>> df
one two
a 1.0 1.0
b 2.0 2.0
c 3.0 3.0
d NaN 4.0
>>> df.describe()
one two
count 3.0 4.000000
mean 2.0 2.500000
std 1.0 1.290994
min 1.0 1.000000
25% 1.5 1.750000
50% 2.0 2.500000
75% 2.5 3.250000
max 3.0 4.000000
其中count
是各列的數據個數,mean
是各列數據的平均值,std
則對應標準差,後續的各行爲從最小值到最大值的均勻數據。
還可以使用median
方法主動求出平均值:
>>> df.median() # 也適用於 Series
one 2.0
two 2.5
dtype: float64
2.3 其他
Series 和 DataFrame 都可以使用同樣的方法轉換爲numpy
數組的形式:
>>> s.to_numpy()
array([ 5., -4., 7., -8., 9.])
此外,兩種主要數據結構還有一個叫做apply
的方法,用來對實例調用指定的函數。可以指定已有的函數,也可以臨時定義一個匿名函數,後者更加常見一些:
>>> df.apply(len)
one 4
two 4
dtype: int64
>>> df.apply(lambda x: x * x)
one two
a 1.0 1.0
b 4.0 4.0
c 9.0 9.0
d NaN 16.0
3. 讀取數據
實際上,大多數時候我們並不會手動創建一個 Series 或是 DataFrame,更一般的方法是通過使用pandas
的讀寫接口,直接從文件中讀取需要處理的數據。
爲了演示pandas
讀取數據的功能,我們提供了一個真實的數據集,其中包含加利福尼亞州住房數據。同學們可以從“代碼示例”獲取該文件;也可直接使用示例代碼中指定的 URL 進行下載。
california_housing_dataframe = pd.read_csv("/data/california_housing_train.csv", sep=",")
使用columns
來看看有哪些種類的數據:
>>> california_housing_dataframe.columns
Index(['longitude', 'latitude', 'housing_median_age', 'total_rooms',
'total_bedrooms', 'population', 'households', 'median_income',
'median_house_value'],
dtype='object')
pandas
提供了大量函數用於文件讀寫,適用於 CSV、Excel、HDF、SQL、JSON、HTML 等文件類型,還包括一個讀取系統剪貼板的接口pd.read_clipboard()
。
4. pandas 畫圖
上一節我們讀取了一個真實的數據集,現在讓我們針對其中意義最豐富的屬性median_house_value
(即平均房價)這一列,來畫個直方圖瞧一瞧:
>>> import matplotlib.pyplot as plt
>>> california_housing_dataframe.hist('median_house_value')
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x00000222934BC8D0>]],
dtype=object)
>>> plt.show()
然後我們使用latitude
作爲 x 軸,以latitude
作爲 y 軸,考察一下加州房價在南北走向上的分佈:
>>> california_housing_dataframe.plot.scatter(y='median_house_value',x='latitude')
<matplotlib.axes._subplots.AxesSubplot object at 0x000002229301C7B8>
>>> plt.show()
從圖上我們可以看到,加州的房價在南北走向上有兩個高價帶。
再考察一下東西走向上的分佈:
>>> california_housing_dataframe.plot.scatter(y='median_house_value',x='longitude')
<matplotlib.axes._subplots.AxesSubplot object at 0x00000222939A87B8>
>>> plt.show()
可以看到,加州房價在東西走向上也出現了兩個高價帶。顯然加州高價房應該是集中在兩個區域,我們可以通過對應的經緯度,找到這兩個區域。
最後我們將經緯度分別作爲 x、y 軸,將平均房價作爲 z 軸畫出一個三維圖像,直觀地觀察一下:
>>> from mpl_toolkits.mplot3d import Axes3D
>>> x = california_housing_dataframe['longitude']
>>> y = california_housing_dataframe['latitude']
>>> z = california_housing_dataframe['median_house_value']
>>> fig = plt.figure()
>>> ax = Axes3D(fig)
>>> ax.scatter(x,y,z)
<mpl_toolkits.mplot3d.art3d.Path3DCollection object at 0x00000222943317B8>
>>> plt.show()
通過拖動 3D 圖像轉換視角,容易看出確實有兩個區域集中分佈着高價房。
5. 總結
本文初步介紹了pandas
模塊中最核心的兩個數據類型:Series 和 DataFrame,以及它們的一些性質。
通過演示讀取數據和使用pandas
畫圖,我們熟悉了pandas
的基本操作,也感受了一下數據可視化的效果。
示例代碼:Python-100-days
參考資料
關注公衆號:python技術,回覆"python"一起學習交流