1. 程式人生 > >資料分析之Pandas——資料結構

資料分析之Pandas——資料結構

資料結構介紹

Pandas的資料物件中都包含最基本的屬性,如資料型別,索引,標籤等。
要使用Pandas的資料結構首先需要引入pandas和numpy:

In [1]: import numpy as np
In [2]: import pandas as pd

有一個基本原則要牢記:資料對齊是預設的。資料和標籤的關聯不會被破壞,除非認為更改。
本章將做一個關於資料結構的簡介,然後在其他章節中做更全面的介紹。

Series

Series 是一維標籤陣列,能夠儲存任何資料型別(整型,浮點型,字串或其他Python物件型別)。軸標籤被稱為索引。建立一個最基本的Series結構,程式碼如下:

s = pd.Series(data, index=index)

data可以是很多型別:

  • 一個 Python 字典
  • 一個 ndarray 物件
  • 一個標量值(如5)

index是軸標籤的列表。因此,這將根據data的不同分為幾種情況:

由ndarray構造

如果傳遞的data是一個ndarray物件,index的長度必須和data的長度保持一致。如果沒有對index引數賦值,那麼索引值會預設為[0, … , len(data) -1],即由0開始,與data資料等長的遞增列表。

In [3]: s = pd.Series(np.random.randn(5), index=['a'
, 'b', 'c', 'd', 'e'])

這裡構造了一個名為s的Serise物件,其data為5個隨機數,那麼對應的index長度也為5, 分別為a至e的小寫字母。
那麼s打印出來的結果如下:

In [4]: s
Out[4]: 
a   -1.317092
b    0.898475
c   -0.026741
d   -0.090660
e   -0.783084
dtype: float64

通過輸入s.index可以檢視索引

In [6]: s.index
Out[6]: Index([u'a', u'b', u'c', u'd', u'e'], dtype='object')

如果不為index引數賦值,那麼構造的Series物件結果為

In [7]: s = pd.Series(np.random.randn(5))

In [8]: s
Out[8]: 
0   -2.750907
1    2.402623
2   -2.479244
3    1.826535
4   -1.270192
dtype: float64

注意:從v0.8.0開始,pandas可以支援索引值不唯一。
如:
In [6]: s = pd.Series(np.random.randn(5), index=[‘a’, ‘b’, ‘b’, ‘c’, ‘d’])
In [7]: s
Out[7]:
a 0.285951
b -0.153731
b 0.536586
c 2.156944
d -0.113776
dtype: float64

由dict 字典型別構造

如果傳遞的data是一個dict字典型別物件,並且傳遞了index引數,那麼對應的值將從字典中取出。

In [8]: d = {'a':0, 'b':1, 'c':2}

In [11]: pd.Series(d, index=['b','c','d', 'a'])
Out[11]: 
b     1
c     2
d   NaN
a     0
dtype: float64

注意:NaN(not a number)是Pandas中使用的資料缺失的標記,由於data中沒有包含key為’d’的資料,所以返回資料缺失,標記為NaN。

否則,index的值將由字典物件裡的key值進行構造。

In [8]: d = {'a':0, 'b':1, 'c':2}

In [9]: pd.Series(d)
Out[9]: 
a    0
b    1
c    2
dtype: int64

由標量值構造

如果傳遞的data是一個標量值,那麼Index引數必須提供。其構造的二維陣列物件將根據索引的長度進行重複。

In [12]: pd.Series(5, index=['b', 'c', 'd', 'a'])
Out[12]: 
b    5
c    5
d    5
a    5
dtype: int64

Series類似於ndarray

Serise扮演的角色非常類似ndarray,並且它可以作為大多數Numpy函式的引數。也可以通過對索引切割來進行資料切片。

In [14]: s[0]
Out[14]: 0.28595142701029524

In [15]: s[:3]
Out[15]: 
a    0.285951
b   -0.153731
b    0.536586
dtype: float64

In [16]: s[s > s.median()]
Out[16]: 
b    0.536586
c    2.156944
dtype: float64

In [17]: s[[4, 3, 1]]
Out[17]: 
d   -0.113776
c    2.156944
b   -0.153731
dtype: float64

In [18]: np.exp(s)
Out[18]: 
a    1.331028
b    0.857502
b    1.710159
c    8.644678
d    0.892458
dtype: float64

Series類似於dict

Series類似於定長的字典物件,你可以通過index標籤獲取或設定值。

In [19]: s['a']
Out[19]: 0.28595142701029524

In [20]: s['c'] = 12

In [21]: s
Out[21]: 
a     0.285951
b    -0.153731
b     0.536586
c    12.000000
d    -0.113776

In [22]: 'b' in s
Out[22]: True

In [23]: 'e' in s
Out[23]: False

如果輸入的標籤不存在,那麼將報異常:

In [24]: s['e']
KeyError: 'e'

如果使用get方法,不存在的標籤將會返回空值或指定預設值:

In [25]: s.get('e')

In [26]: s.get('e', np.nan)
Out[26]: nan

向量操作和標籤對齊

當做資料分析時, 和Numpy的陣列一樣,一個一個的迴圈遍歷Series中的值通常是不必要的。Series物件也可以像ndarray一樣,傳遞到大多數Numpy方法中。

In [27]: s + s
Out[27]: 
a     0.571903
b    -0.307463
b     1.073173
c    24.000000
d    -0.227552
dtype: float64

In [28]: s * 2
Out[28]: 
a     0.571903
b    -0.307463
b     1.073173
c    24.000000
d    -0.227552
dtype: float64

In [29]: np.exp(s)
Out[29]: 
a         1.331028
b         0.857502
b         1.710159
c    162754.791419
d         0.892458
dtype: float64

Series和ndarray關鍵的區別在於,Series間的操作會自動根據標籤對齊資料。因此,你可以直接編寫計算,而不用考慮所涉及到的Series是否具有相同的標籤。

In [30]: s[1:] + s[:-1]
Out[30]: 
a          NaN
b    -0.307463
b     0.382855
b     0.382855
b     1.073173
c    24.000000
d          NaN
dtype: float64

在未對齊的Series間操作,結果將包含索引的集合。如果標籤在一個或另一個Series中未找到,結果將標記為缺失NaN。所以可以在不進行任何顯示資料對齊的情況下編寫程式碼,在互動資料分析和研究中提供了巨大的自由度和靈活性。Pandas資料結構的綜合資料排列特徵使Pandas有別於大多數用於標記資料的相關工具。

Name 屬性

Series也有Name屬性

In [31]: s = pd.Series(np.random.randn(5), name='something')

In [32]: s
Out[32]: 
0    1.522774
1    0.733561
2   -0.702462
3    0.022205
4    1.704067
Name: something, dtype: float64

許多情況下,Series的Name將被自動分配,特別是下面即將看到的對於DataFrame的一維切片時。
可以通過方法pandas.Series.rename()對Series進行重新命名。

In [33]: s2 = s.rename("different")

In [34]: s2.name
Out[35]: 'different'

注意s和s2分別引用的是兩個不同的物件。

DataFrame

DataFrame是一個2維標籤的資料結構,它的列可以存在不同的型別。你可以把它簡單的想成Excel表格或SQL Table,或者是包含字典型別的Series。它是最常用的Pandas物件。和Series一樣,DataFrame接受許多不同的型別輸入:

  • 包含1維ndarray,列表物件,字典物件或者Series物件的字典物件
  • 2維的ndarray物件
  • 結構化或記錄型的ndarray
  • Series物件
  • 另一個DataFrame物件

可以通過傳遞索引(行標籤)和列(列標籤)引數來操作資料。如果傳遞了索引和/或列,可以得到包含索引和/或列的DataFrame結果集。因此,一個字典型別的Series加上一個特定的索引,將會丟棄所有與傳遞的所以不匹配的資料。

如果沒有傳遞軸標籤,他們將基於常用規則的輸入資料進行建立。

由包含Series的字典或巢狀字典構造

結果的索引將是各個Series索引的並集。如果有任何巢狀的字典物件,都將先轉換成Series。如果沒有傳遞任何列,那麼列將是已排序的字典物件的Key值。

In [20]: d = {'one' : pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
    ...: 'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}

In [21]: df = pd.DataFrame(d)

In [22]: df
Out[22]: 
   one  two
a    1    1
b    2    2
c    3    3
d  NaN    4

In [23]: pd.DataFrame(d, index=['d', 'b', 'a'])
Out[23]: 
   one  two
d  NaN    4
b    2    2
a    1    1

In [24]: pd.DataFrame(d, index=['d', 'b', 'a'], columns=['two', 'three'])
Out[24]: 
   two three
d    4   NaN
b    2   NaN
a    1   NaN

通過訪問索引和列屬性,可以分別訪問行和列標籤:

In [25]: df.index
Out[25]: Index([u'a', u'b', u'c', u'd'], dtype='object')

df.columns
Out[26]: Index([u'one', u'two'], dtype='object')

由包含ndarray或列表的字典構造

ndarray的長度必須一致。如果一個索引被傳遞,它必須與陣列的長度相同。如果沒有索引被傳遞,結果將是range(n),n是陣列的長度。

In [27]: d = {'one':[1., 2., 3., 4.],
    ...: 'two': [4., 3., 2., 1.]}

In [28]: pd.DataFrame(d)
Out[28]: 
   one  two
0    1    4
1    2    3
2    3    2
3    4    1

In [29]: pd.DataFrame(d, index=['a', 'b', 'c', 'd'])
Out[29]: 
   one  two
a    1    4
b    2    3
c    3    2
d    4    1

由陣列構造

這個例子的處理與陣列字典的完全相同

In [39]: data = np.zeros((2,), dtype=[('A', 'i4'), ('B', 'f4'), ('C', 'a10')])

In [40]: data[:] = [(1, 2., 'Hello'), (2, 3., 'World')]

In [41]: pd.DataFrame(data)
Out[41]: 
   A  B      C
0  1  2  Hello
1  2  3  World

In [42]: pd.DataFrame(data, index=['first', 'second'])
Out[42]: 
        A  B      C
first   1  2  Hello
second  2  3  World

In [43]: pd.DataFrame(data, columns=['C', 'A', 'B'])
Out[43]: 
       C  A  B
0  Hello  1  2
1  World  2  3

注意:DataFrame的工作方式與2維的ndarray並不一樣

由包含字典的列表構造

In [44]: data = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]

In [45]: pd.DataFrame(data)
Out[45]: 
   a   b   c
0  1   2 NaN
1  5  10  20

In [47]: pd.DataFrame(data, index=['first', 'second'])
Out[47]: 
        a   b   c
first   1   2 NaN
second  5  10  20

In [48]: pd.DataFrame(data, columns=['a', 'b'])
Out[48]: 
   a   b
0  1   2
1  5  10

由包含元組的字典構造

In [49]: pd.DataFrame({('a', 'b'): {('A', 'B'): 1, ('A', 'C'): 2},
    ...:               ('a', 'a'): {('A', 'C'): 3, ('A', 'B'): 4},
    ...:               ('a', 'c'): {('A', 'B'): 5, ('A', 'C'): 6},
    ...:               ('b', 'a'): {('A', 'C'): 7, ('A', 'B'): 8},
    ...:               ('b', 'b'): {('A', 'D'): 9, ('A', 'B'): 10}})
Out[49]: 
      a           b    
      a   b   c   a   b
A B   4   1   5   8  10
  C   3   2   6   7 NaN
  D NaN NaN NaN NaN   9

由Series構造

結果將是索引與輸入的Series相同,並且有一列資料,列名與Series的名稱一致(僅在沒有提供其他列名的情況下)。

In [58]: s = pd.Series([1, 2, 3], index=['a', 'b', 'c'], name='first')

In [59]: s
Out[59]: 
a    1
b    2
c    3
Name: first, dtype: int64

In [60]: pd.DataFrame(s)
Out[60]: 
   first
a      1
b      2
c      3

缺失資料

跟多的缺失資料相關內容將在其他章節介紹。為了構造一個包含缺失資料的DataFrame,對於那些缺失的值需要用到np.nan。或者,將numpy.MaskedArray作為data引數傳遞給DataFrame的建構函式,它所遮蓋的條目將被認為是缺失的。

其他構造方式

DataFrame.from_dict

DataFrame.from_dict將獲取一個巢狀字典或者陣列字典,並返回一個DataFrame。它的操作方式類似於DataFrame的建構函式,除了orient引數預設為column,但是可以將它設定為index,讓字典的key值作為行標籤。

DataFrame.from_records
DataFrame.from_records將獲取一個元組構成的列表或者一個結構化的ndarray物件。與普通的DataFrame建構函式類似,除了索引可能是結構化的dtype的特定欄位。例如:

In [61]: data
Out[61]: [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]

In [62]: data = np.zeros((2,), dtype=[('A', 'i4'),('B', 'f4'),('C', 'a10')])

In [63]: data[:] = [(1, 2, 'Hello'), (2, 3, 'World')]

In [64]: data
Out[64]: 
array([(1, 2.0, 'Hello'), (2, 3.0, 'World')], 
      dtype=[('A', '<i4'), ('B', '<f4'), ('C', 'S10')])

In [66]: pd.DataFrame.from_records(data, index='C')
Out[66]: 
       A  B
C          
Hello  1  2
World  2  3

DataFrame.from_items
DataFrame.from_items 執行機制類似於dict的建構函式,傳遞一個鍵值對序列作為引數。Key是列名(或索引名,orient=’index’),Value是列的值(或行的值)。這對於構造具有特定順序的列的DataFrame是很有用的,而不用傳遞列的明確列表:

In [68]: pd.DataFrame.from_items([('A', [1, 2, 3]), ('B', [4, 5, 6])])
Out[68]: 
   A  B
0  1  4
1  2  5
2  3  6

如果傳遞orient=’index’,key值將作為索引標籤。但是在下面的例子中任需要列名:

In [73]: pd.DataFrame.from_items([('A', [1, 2, 3]), ('B', [4, 5, 6])], orient='index', columns=['one', 'two', 'three'])
Out[73]: 
   one  two  three
A    1    2      3
B    4    5      6

列的選擇、新增和刪除

類似於對字典操作的語法,你可以對一個DataFrame進行獲取列的值,對列賦值或刪除列。

In [74]: df['one']
Out[74]: 
a     1
b     2
c     3
d   NaN
Name: one, dtype: float64

In [75]: df['three'] = df['one'] * df['two']

In [76]: df['flag'] = df['one'] > 2

In [77]: df
Out[77]: 
   one  two  three   flag
a    1    1      1  False
b    2    2      4  False
c    3    3      9   True
d  NaN    4    NaN  False

列可以類似於dict一樣,刪除或者取出

In [78]: del df['two']

In [79]: three = df.pop('three')

In [80]: df
Out[80]: 
   one   flag
a    1  False
b    2  False
c    3   True
d  NaN  False

In [81]: three
Out[81]: 
a     1
b     4
c     9
d   NaN
Name: three, dtype: float64

當插入一個標量值時,它會自動的填滿整列

In [82]: df['foo'] = 'bar'

In [83]: df
Out[83]: 
   one   flag  foo
a    1  False  bar
b    2  False  bar
c    3   True  bar
d  NaN  False  bar

當插入一個與DataFrame沒有相同索引的Series時,它將匹配DataFrame的索引

In [84]: df['one_trunc'] = df['one'][:2]

In [85]: df
Out[85]: 
   one   flag  foo  one_trunc
a    1  False  bar          1
b    2  False  bar          2
c    3   True  bar        NaN
d  NaN  False  bar        NaN

也可以插入ndarray,但是它的長度必須與DataFrame的索引長度匹配。

預設情況下,列在最後插入。insert函式可用於插入在列的制定位置:

In [87]: df
Out[87]: 
   one  bar   flag  foo  one_trunc
a    1    1  False  bar          1
b    2    2  False  bar          2
c    3    3   True  bar        NaN
d  NaN  NaN  False  bar        NaN

通過方法分配新列

DataFrame具有assign()方法,允許你很方便的建立從現有列派生出來的新列。

In [47]: iris = pd.read_csv(u'data/iris.csv')

In [48]: iris.head()
Out[48]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name
0          5.1         3.5          1.4         0.2  Iris-setosa
1          4.9         3.0          1.4         0.2  Iris-setosa
2          4.7         3.2          1.3         0.2  Iris-setosa
3          4.6         3.1          1.5         0.2  Iris-setosa
4          5.0         3.6          1.4         0.2  Iris-setosa

In [49]: (iris.assign(sepal_ratio = iris['SepalWidth'] / iris['SepalLength']).head())
Out[49]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name  sepal_ratio
0          5.1         3.5          1.4         0.2  Iris-setosa     0.686275
1          4.9         3.0          1.4         0.2  Iris-setosa     0.612245
2          4.7         3.2          1.3         0.2  Iris-setosa     0.680851
3          4.6         3.1          1.5         0.2  Iris-setosa     0.673913
4          5.0         3.6          1.4         0.2  Iris-setosa     0.720000

以上的例子中,先原有的資料中增加了一個預先計算的值。我們同樣還可以傳遞帶有一個引數的函式

In [50]: iris.assign(sepal_ratio = lambda x: (x['SepalWidth']/x['SepalLength'])).head()
Out[50]: 
   SepalLength  SepalWidth  PetalLength  PetalWidth         Name  sepal_ratio
0          5.1         3.5          1.4         0.2  Iris-setosa     0.686275
1          4.9         3.0          1.4         0.2  Iris-setosa     0.612245
2          4.7         3.2          1.3         0.2  Iris-setosa     0.680851
3          4.6         3.1          1.5         0.2  Iris-setosa     0.673913
4          5.0         3.6          1.4         0.2  Iris-setosa     0.720000

assign 始終返回資料的副本,讓原始的DataFrame保持原樣。

傳遞一個可呼叫的,而不是要插入的實際值,當你沒有對DataFrame引用時,這是非常有幫助的。在操作鏈中使用assign,這是很常見的。例如,我們可以把DataFrame限定為花萼長度大於5的資料,然後計算這個比例,進行繪圖:

In [51]: (iris.query('SepalLength > 5')
    ...:     .assign(SepalRatio = lambda x: x.SepalWidth / x.SepalLength,
    ...:             PetalRatio = lambda x: x.PetalWidth / x.PetalLength)
    ...:     .plot(kind='scatter', x='SepalRatio', y='PetalRatio'))
Out[51]: <matplotlib.axes._subplots.AxesSubplot at 0x1049b8b0>

這裡寫圖片描述
當函式被傳入,函式將在分配給DataFrame時進行計算。重點時,這是將資料過濾為Sepal 長度大於5的行。資料首先被過濾,然後再進行比例的計算。

assign方法的引數 **kwargs。key是列名,value是要插入的值(如Series或Numpy的 array),或者是含有一個引數的函式。呼叫後將返回,原有的DataFrame的副本加上新增的值。

警告:由於方法的引數為**kwargs,一個字典型別,所以產生的DataFrame中的新列的順序不能保證與你傳入的順序相同。為了讓結果可以預測,在DataFrame的末尾,資料條目將按字母順序插入。

所有的表示式先行計算,再分配。因此,不能引用另一個在呼叫時分配的列。比如:

`In [74]: # 不用這樣做, 無法引用‘C’列
df.assign(C = lambda x: x[‘A’] + x[‘B’],
D = lambda x: x[‘A’] + x[‘C’])
In [2]: # 更改為呼叫兩次assign方法
(df.assign(C = lambda x: x[‘A’] + x[‘B’])
.assign(D = lambda x: x[‘A’] + x[‘C’]))’

索引/選擇

索引的基本操作如下:

操作 語法 結果
選擇列 df[col] Series
根據標籤選擇行 df.loc[lable] Series
根據位置選擇行 df.iloc[loc] Series
行切片 df[5:10] DataFrame
根據條件選擇行 df[bool_vec] DataFrame

行選擇器,如,根據索引返回列的Series:

#沿用之前的df物件
In [63]: df
Out[63]: 
   one  bar   flag  foo  one_trunc
a    1    1  False  bar          1
b    2    2  False  bar          2
c    3    3   True  bar        NaN
d  NaN  NaN  False  bar        NaN

#根據索引標籤選擇
In [64]: df.loc['b']
Out[64]: 
one              2
bar              2
flag         False
foo            bar
one_trunc        2
Name: b, dtype: object

#根據位置選擇
In [65]: df.iloc[2]
Out[65]: 
one             3
bar             3
flag         True
foo           bar
one_trunc     NaN
Name: c, dtype: object

資料對齊和運算

DataFrame物件之間的資料對齊會自動在列和索引(行標籤)上對齊。同樣,生產的物件將是列和行標籤的並集。

In [69]: df = pd.DataFrame(np.random.randn(10, 4), columns=['A', 'B', 'C', 'D'])

In [70]: df
Out[70]: 
          A         B         C         D
0 -1.081213  0.964799 -1.526936  0.857095
1  0.786559 -0.824999  0.373886  0.383198
2 -0.026515 -0.539306  0.987269  0.045101
3 -0.726887 -1.176843 -0.799625 -0.192155
4 -1.180493  2.145532  0.682645  0.317200
5  1.041298 -1.334093  0.346744 -0.222402
6 -0.553535 -1.031090 -1.738747 -0.404298
7  0.367074 -1.312607  0.811453 -0.829041
8  1.150281 -0.435246  0.686140  1.672713
9 -2.811454 -0.064040 -0.173748  0.156016

In [71]: df2 = pd.DataFrame(np.random.randn(7, 3), columns=['A', 'B', 'C'])

In [72]: df2
Out[72]: 
          A         B         C
0 -0.116847  2.508202 -0.206053
1 -0.264634 -0.440654  0.355929
2 -0.805070  1.162288  0.637293
3 -0.423643  0.854117 -0.385428
4  0.790752  0.084708 -0.699494
5  2.139285 -0.546327  0.381495
6 -0.086828  1.019701  0.448619

In [73]: df + df2
Out[73]: 
          A         B         C   D
0 -1.198059  3.473001 -1.732989 NaN
1  0.521925 -1.265653  0.729814 NaN
2 -0.831585  0.622982  1.624562 NaN
3 -1.150530 -0.322726 -1.185053 NaN
4 -0.389741  2.230240 -0.016849 NaN
5  3.180583 -1.880420  0.728239 NaN
6 -0.640363 -0.011389 -1.290128 NaN
7       NaN       NaN       NaN NaN
8       NaN       NaN       NaN NaN
9       NaN       NaN       NaN NaN

當在DataFrame和Series之間進行操作時,預設的行為是在DataFrame列上對其Series索引,然後安裝行的寬度進行廣播。例如:

In [74]: df.iloc[0]
Out[74]: 
A   -1.081213
B    0.964799
C   -1.526936
D    0.857095
Name: 0, dtype: float64

In [75]: df - df.iloc[0]
Out[75]: 
          A         B         C         D
0  0.000000  0.000000  0.000000  0.000000
1  1.867771 -1.789798  1.900821 -0.473897
2  1.054698 -1.504105  2.514205 -0.811994
3  0.354326 -2.141642  0.727311 -1.049250
4 -0.099280  1.180733  2.209580 -0.539896
5  2.122511 -2.298892  1.873680 -1.079498
6  0.527677 -1.995888 -0.211811 -1.261393
7  1.448286 -2.277405  2.338389 -1.686136
8  2.231494 -1.400045  2.213076  0.815618
9 -1.730241 -1.028839  1.353188 -0.701080

在處理時間Series資料的特殊情況下,並且DataFrame索引也包含日期,將根據列的寬度進行廣播:

In [76]: index = pd.date_range('1/1/2000', periods=8)

In [77]: index
Out[77]: 
DatetimeIndex(['2000-01-01', '2000-01-02', '2000-01-03', '2000-01-04',
               '2000-01-05', '2000-01-06', '2000-01-07', '2000-01-08'],
              dtype='datetime64[ns]', freq='D', tz=None)

In [78]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=list('ABC'))

In [79]: df
Out[79]: 
                   A         B         C
2000-01-01  1.658336  0.312690  0.434380
2000-01-02  1.588613  1.044227 -0.548043
2000-01-03  1.453697  0.634530 -1.125464