Series 和 index 簡介
創(chuàng)建 Series
pd.Series 主要設(shè)置兩個參數(shù),data 和 index,如果不設(shè)置 index,則 index 從 0 開始遞增。除此之外,還可以設(shè)置 dtype。
import pandas as pd
s = pd.Series([20,30,40,50], index=['Eve', 'Bill', 'Lis', 'Bob'])
s
# Eve 20
# Bill 30
# Liz 40
# Bob 50
# dtype: int64
s.index
# Index(['Eve', 'Bill', 'Liz', 'Bob'], dtype='object')
s.values
# array([20, 30, 40, 50], dtype=int64)
除了直接設(shè)置各個字段,還可以將字典作為參數(shù)傳入,series 會自動將 key 作為 index,將 value 作為 data。
d = {k:v**2 for k, v in zip('abcdefghij', range(10))}
s = pd.Series(d, dtype='int64')
為了保證數(shù)據(jù)存取的效率,series 的 index 必須是可哈希的。
索引
series 和字典一樣,索引和修改的時間復(fù)雜度為 。它有兩種索引方式,第一種索引方式與字典相同,但強烈不推薦,會導(dǎo)致各種副作用:
# 下面這種方法強烈不推薦
s['Eve']
# 20
s['Eve':'Liz']
# Eve 20
# Bill 30
# Liz 40
# dtype: int64
在某些情況下,它會產(chǎn)生歧義:
s = pd.Series(['a', 'b', 'c'], index=[2, 0, 1])
print(s[0])
# 'b'
print(s[0:1])
# 'a'
# 即,當(dāng)做切片時,是位置優(yōu)先的,當(dāng)獲取某個元素時,是 label 優(yōu)先的
# 為了解決這種歧義,pandas 引入了 loc 和 iloc 方法,前者索引的是 label,后者是位置下標(biāo)。
第二種索引方式是采用 .loc 方法,這種寫法是推薦的:
s.loc[['Eve']
# 20
s.loc['Eve':'Liz']
# Eve 20
# Bill 30
# Liz 40
# dtype: int64
注意一個細節(jié):利用 index 做切片時,和 list 或者 array 利用下標(biāo)切片,不包括末尾元素不同,index 切片是包括 end 元素的。
除了利用 index 切片,Series 也可以用下標(biāo)切片,此時不包括終點下標(biāo)對應(yīng)的元素,表現(xiàn)和 python 慣例一致:
s.iloc[0] # 等價于 s.loc['Eve']
s.iloc[0:3]
# Eve 20
# Bill 30
# Liz 40
# dtype: int64
不像字典的 key 是唯一的,Series 支持 index 包含重復(fù)元素。但對 Series 做切片時,如果重復(fù)的 index 不是相鄰的,則會報錯:"Cannot get left\right slice bound for non-unique label: 'xxx' "。
animaux = ['chien', 'chat', 'chat', 'chien', 'poisson']
proprio = ['eve', 'bob','eve','bill','Liz']
s = pd.Series(animaux, index=proprio)
s
# eve chien
# bob chat
# eve chat
# bill chien
# Liz poisson
# dtype: object
s.loc['eve':'liz']
# KeyError: "Cannot get left slice bound for non-unique label: 'eve'"
因此強烈建議先對 index 排序,這樣可以保證切片能夠一直能正確運行,并且還能提高索引的效率。
s = s.sort_index()
s
# Liz poisson
# bill chien
# bob chat
# eve chien
# eve chat
# dtype: object
# 也可以對 sort_index 設(shè)置 key 參數(shù):
s = s.sort_index(key = lambda x: x.str.lower())
# bill chien
# bob chat
# eve chien
# eve chat
# Liz poisson
# dtype: object
和 numpy 一樣,Series 也支持高級索引:
s == 'chien'
# bill True
# bob False
# eve True
# eve False
# Liz False
# dtype: bool
s.loc[(s=='chien') | (s=='poisson')]
# bill chien
# eve chien
# Liz poisson
# dtype: object
s.loc[(s=='chien') | (s=='poisson')] = 'autre'
# bill autre
# bob chat
# eve autre
# eve chat
# Liz autre
# dtype: object
合并
兩個 Series 可以相加,只有相同 label 的數(shù)據(jù)會相加,只存在于其中一個 Series 的數(shù)據(jù)相加后為 NaN,但也可以指定一方缺失的 label 對應(yīng)的默認值:
s1 = pd.Series([1, 2, 3], index=list('abc'))
s2 = pd.Series([5, 6, 7], index=list('acd'))
s1
# a 1
# b 2
# c 3
# dtype: int64
s1 + s2
# a 6.0
# b NaN
# c 9.0
# d NaN
# dtype: float64
s1.add(s2, fill_value=50)
# a 6.0
# b 52.0
# c 9.0
# d 57.0
# dtype: float64
數(shù)據(jù)類型
需要注意的是,在操作過程中, series value 的數(shù)據(jù)類型可能會隱式地被改變,如果不注意,很有可能影響增刪的效率,甚至產(chǎn)生錯誤的結(jié)果。
s = pd.Series({k:v**2 for k, v in zip('abcdefghij', range(10))})
print(s.values.dtype) # int64
s['c'] = 'spam'
print(s.values.dtype) # object
影響效率的例子:
s = pd.Series(range(10_000))
print(s.values.dtype) # int64
%timeit s**2
# 158 μs ± 18.3 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
s[10_000] = 'spam'
del s[10_000] # 增加了一條數(shù)據(jù),又刪除,導(dǎo)致了數(shù)據(jù)雖然沒變,但是數(shù)據(jù)類型改變了
print(s.values.dtype) # object
%timeit s**2
# 3.95 ms ± 1.01 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
產(chǎn)生錯誤結(jié)果的例子:
s = pd.Series([1,2,3])
s[3] = '4'
print(s+s)
# 0 2
# 1 4
# 2 6
# 3 44
# dtype: object
DataFrame 簡介
創(chuàng)建 DataFrame
- 可以通過多個 Series 創(chuàng)建 DataFrame:
age = pd.Series([30, 20, 50], index = ['alice', 'bob', 'julie])
height = pd.Series([150, 170, 168], index=['alice', 'marc', 'julie'])
# 將字典作為參數(shù)傳入 pd.DataFrame
# 其中字典的鍵為 DataFrame 的列,值為 Series
stat = pd.DataFrame({'age': age, 'height':height})
print(stat)
"""
age height
alice 30.0 150.0
bob 20.0 NaN
julie 50.0 168.0
marc NaN 170.0"""
# 可以看到,DataFrame 將缺失的值設(shè)為 NaN
DataFrame 還支持廣播功能:
stat = pd.DataFrame({'age': age, 'height':height, 'city':Nice })
print(stat)
# age height city
# alice 30.0 150.0 Nice
# bob 20.0 NaN Nice
# julie 50.0 168.0 Nice
# marc NaN 170.0 Nice
- 還可以通過 numpy array 創(chuàng)建 DataFrame
a = np.random.randint(0,20,9).reshape(3,3)
p = pd.DataFrame(a, index=list('abc'), columns=list('xyz'))
print(p)
# x y z
# a 3 1 0
# b 2 5 1
# c 13 18 5
存取 DataFrame
可以將 DataFrame 保存為 csv 文件或 json 文件
p.to_csv('my_data.csv') # 保存為 csv 文件
p.to_json('my_data.json') # 保存為 json 文件
p2 = pd.read_json('my_data.json')
print(p2) # 和 p 相同
p3 = pd.read_csv('my_data.csv')
print(p3) # 注意,默認情況下 p3 的 index 為 0,1,2,而 a b c 會被認為是一列
# Unnamed: 0 x y z
# 0 a 3 1 0
# 1 b 2 5 1
# 2 c 13 18 5
# 為了避免上面的情況,需要告訴 pandas 第一列為 index 列
p4 = pd.read_csv('my_data.csv', index_col=0)
print(p4)
# x y z
# a 3 1 0
# b 2 5 1
# c 13 18 5
編輯 DataFrame
prenoms = ['liz', 'bob', 'bill', 'eve']
age = pd.Series([25, 30, 35, 40], index = prenoms)
taille = pd.Series([160, 175, 170, 180], index = prenoms)
sexe = pd.Series(list('fhhf'), index = prenoms)
df = pd.DataFrame({'age': age, 'taille': taille, 'sexe': sexe})
print(df)
# age taille sexe
# liz 25 160 f
# bob 30 175 h
# bill 35 170 h
# eve 40 180 f
-
df.reset_index將原來的 index 變成一個 column,并創(chuàng)建新的 index,以數(shù)字為編號:
df = df.reset_index()
print(df)
index age taille sexe
# 0 liz 25 160 f
# 1 bob 30 175 h
# 2 bill 35 170 h
# 3 eve 40 180 f
-
df.rename(columns={'index': 'prenom','taille':'height'})對某列或某幾列改名。
df = df.rename(columns={'index': 'prenom', 'taille': 'height'})
print(df)
# prenom age height sexe
# 0 liz 25 160 f
# 1 bob 30 175 h
# 2 bill 35 170 h
# 3 eve 40 180 f
-
df.set_index('age')取出某列,作為新的 index。
df = df.set_index('age')
print(df)
查看元素
包括一系列函數(shù):
import seaborn as sns
tips = sns.load_dataset('tips') # 加載一個現(xiàn)成的DataFrame
tips.head(n=2) # 展示前兩行
tips.tail(n=3) # 展示后三行
# 也可以不設(shè)置 n
names = ['alice', 'bob', 'marc', 'bill', 'sonia']
# créons trois Series qui formeront les trois colonnes
age = pd.Series([12, 13, 16, 11, 16], index=names)
height = pd.Series([130, 140, 176, 120, 165], index=names)
sex = pd.Series(list('fmmmf'), index=names)
# créons maintenant la DataFrame
p = pd.DataFrame({'age': age, 'height': height, 'sex': sex})
print(p)
p.index
# Index(['alice', 'bob', 'marc', 'bill', 'sonia'], dtype='object')
p.columns
# Index(['age', 'height', 'sex'], dtype='object')
p.values
# array([[12, 130, 'f'],
# [13, 140, 'm'],
# [16, 176, 'm'],
# [11, 120, 'm'],
# [16, 165, 'f']], dtype=object)
p.T
# alice bob marc bill sonia
# age 12 13 16 11 16
# height 130 140 176 120 165
# sex f m m m f
p.describe() 默認只顯示數(shù)字列,但也可以設(shè)置參數(shù) include='all' 現(xiàn)實所有列。
p.loc['sonia'] 展示 sonia 行
p.loc['sonia', 'age'] 只顯示 sonia 的年齡
根據(jù)條件篩選元素
篩選出女性條目:
b = p.loc[:, 'sex'] == f # b 是一個 series
b.values # array([ True, False, False, False, True])
b.index # Index(['alice', 'bob', 'marc', 'bill', 'sonia'], dtype='object')
p.loc[b, :]
# age height sex
# alice 12 130 f
# sonia 16 165 f
增加年齡篩選條件:
p.loc[(p.loc[:, 'sex']=='f') &(p.loc[:, 'age']>14), :]
# age height sex
# sonia 16 165 f
DataFrame.mean() 可以按列計算平均值
幾種不建議的寫法:
p['sex'] # 返回 sex 列
# 但是
p['alice': 'marc'] # 返回的是行,從 alice 到 marc(包含 marc)
p.age # 返回 age 列
# 但是,如果 p 本身有 age 這個 attribute 則優(yōu)先返回 attribute
p.age = 3 # 設(shè)置與 age 列同名的 attribute,那么下次調(diào)用 p.age 時,優(yōu)先返回 3
# 再比如,為 p 增加一列 mean
p['mean'] = 1
# 但是,由于 DataFrame 本身有 mean,那么 p.mean 并不會返回 mean 這列
p.drop(columns=['mean', ], inplace=True) 用于刪除一列或多列,inplace 作用是,設(shè)置是否修改原來的 p,如果True,返回 None,原 p 被修改,如果 False,返回被修改后的 DataFrame,同時原 p 保留。
Universal functions and pandas
DataFrame 支持所有 numpy 的函數(shù),numpy 函數(shù)可以直接施加在 DataFrame 上,例如:
d = pd.DataFrame(np.random.rand(3,3), columns=list('abc'))
np.log(d) # 直接對 values 取 log,同時保留 行、列的label
但是,如果需要用到 DataFrame 的 label 對齊特性,例如兩個 index 順序并不相同的 DataFrame 相加,那么 numpy 的函數(shù)將直接計算中間的 values,而不會考慮它們 label 對齊的問題。(該問題已經(jīng)在 pandas 0.2.5 中被修正)
運算中設(shè)置 fill_value 可以讓表中缺失的數(shù)據(jù)被 fill_value 代替。
names = ['alice', 'bob', 'charle']
bananas = pd.Series([10, 3, 9], index=names)
oranges = pd.Series([3, 11, 6], index=names)
fruits_jan = pd.DataFrame({'bananas': bananas, 'orange': oranges})
bananas = pd.Series([6, 1], index=names[:-1])
apples = pd.Series([8, 5], index=names[1:])
fruits_feb = pd.DataFrame({'bananas': bananas, 'apples': apples})
print(fruits_jan)
# bananas orange
# alice 10 3
# bob 3 11
# charle 9 6
print(fruits_feb)
# bananas apples
# alice 6.0 NaN
# bob 1.0 8.0
# charle NaN 5.0
# 不設(shè)置 fill_value 時:
eaten_fruits = fruits_jan + fruits_feb
print(eaten_fruits)
# apples bananas orange
# alice NaN 16.0 NaN
# bob NaN 4.0 NaN
# charle NaN NaN NaN
# 設(shè)置后
eaten_fruits = fruits_jan.add(fruits_feb, fillvalue=0.0)
print(eaten_fruits)
# apples bananas orange
# alice NaN 16.0 3.0
# bob 8.0 4.0 11.0
# charle 5.0 9.0 6.0
# 我們發(fā)現(xiàn),如果一個值在兩個表中都是 NaN,那么這個值仍舊是 NaN
當(dāng)一個 Series 和一個 DataFrame 相加時,pandas 會默認 Series 是一行,并把它廣播到其它行。Series 的 index 會被對應(yīng)到 DataFrame 的列上,并對齊。如果 Series 的 index 與 DataFrame 的列沒關(guān)系,那么會擴增 DataFrame,擴增區(qū)域?qū)?yīng)的數(shù)據(jù)為 NaN。如果想讓 Series 的 index 和 DataFrame 的index 對應(yīng),則需要指定 axis=0:
dataframe.add(series_col, axis=0)
字符串操作
- 字符串方法只適用于
Series和Index,不適用于DataFrame - 字符串方法不改變
NaN,同時,把所有非字符串元素轉(zhuǎn)為NaN - 字符串方法返回一個
Series或者Index的拷貝,不改變輸入的對象 - 大多數(shù) Python
str類方法在 pandas 中以向量化的形式存在 - 調(diào)用字符串方法的通用句法為:
Series.str.<vectorized method name>或者Index.str.<vectorized method name>
names = ['alice ', ' bOB', 'Marc', 'bill', 3, ' JULIE ', np.NaN]
age = pd.Series(names)
print(age)
# 0 alice
# 1 bOB
# 2 Marc
# 3 bill
# 4 3
# 5 JULIE
# 6 NaN
# dtype: object
# 將其中的 str 元素變成小寫
a = age.str.lower()
print(a)
# 0 alice
# 1 bob
# 2 marc
# 3 bill
# 4 NaN
# 5 julie
# 6 NaN
# dtype: object
# 所有字符串都變成了小寫,此外其中的非字符串元素被轉(zhuǎn)換為了 NaN,而原本的 NaN 沒變
# 刪掉字符串前后多余的空格
a = a.str.strip()
print(a)
# 0 alice
# 1 bob
# 2 marc
# 3 bill
# 4 NaN
# 5 julie
# 6 NaN
# dtype: object
處理缺失數(shù)據(jù)
在構(gòu)建 pandas Series 或者 DataFrame 時,有兩種方式可以表示 NaN,一種是 np.NaN,另一種是python 的 None 對象。np.NaN 的數(shù)據(jù)類型是 float,因此,在 pandas 中,存在 NaN 的對象要么是 float64 的,要么是 object 類型。
s = pd.Series([1, 2], list('xy'))
print(s)
# x 0
# y 1
# dtype: int64
s.loc['y'] = np.NaN
print(s)
# x 0.0
# y NaN
# dtype: float64
#
# 引入 NaN 后,原本 int64 的數(shù)據(jù)變成了 float64
# 也可以用 Python 的 None 代替 np.NaN,效果是一樣的
s = pd.Series([1, 2], list('xy'))
s.loc['y'] = None
print(s)
# x 0.0
# y NaN
# dtype: float64
pandas 利用如下方法處理缺失數(shù)據(jù):
-
isna()返回一個 mask,在缺失數(shù)據(jù)部分為True,非缺失部分為False(isnull()是它的 alias)。 -
notna()返回一個和isna()相反的 mask,notnull()是它的 alias -
dropna()返回一個沒有NaN的新對象 -
fillna()返回一個NaN被替代的新對象
p = pd.DataFrame([[1, 2, np.NaN], [3, np.NaN, np.NaN], [7, 5, np.NaN]])
print(p)
# 0 1 2
# 0 1 2.0 NaN
# 1 3 NaN NaN
# 2 7 5.0 NaN
# ----------------------------------------
# 默認情況下 .dropna() 會把所有包含 NaN 的《行》都刪除。
print(p.dropna())
# Empty DataFrame
# Columns: [0, 1, 2]
# Index: []
# ---------------------------------------
# 設(shè)置 axis,讓它把所有包含 NaN 的《列》都刪除
print(p.dropna(axis=1))
# 0
# 0 1
# 1 3
# 2 7
# --------------------------------------
# 只有該行/列全部為 NaN 時才會刪除它:
print(p.dropna(axis=1, how='all'))
# 0 1
# 0 1 2.0
# 1 3 NaN
# 2 7 5.0
# ------------------------------------
# thresh=n 可以設(shè)置該行/列至少包含幾個 NaN 才會被刪除
print(p.dropna(thresh = 2))
# 第二行有兩個 NaN,遂被刪除
# 0 1 2
# 0 1 2.0 NaN
# 2 7 5.0 NaN
# --------------------------------------
# fillna() 填充 NaN
print(p.fillna(-1))
# 0 1 2
# 0 1 2.0 -1.0
# 1 3 -1.0 -1.0
# 2 7 5.0 -1.0
# 用 NaN 后面一行的數(shù)據(jù)填充 NaN,bfill:backfill
print(p.fillna(method='bfill'))
# 0 1 2
# 0 1 2.0 NaN
# 1 3 5.0 NaN
# 2 7 5.0 NaN
# 用 NaN 前面一列的數(shù)據(jù)填充 NaN,ffill:forward fill
print(p.fillna(method='ffill', axis=1))
# 0 1 2
# 0 1.0 2.0 2.0
# 1 3.0 3.0 3.0
# 2 7.0 5.0 5.0
MultiIndex
MultiIndex 適用于數(shù)組大于二維的情況。所有可以用 Index 的地方,都可以用 MultiIndex。
MultiIndex Series
print(p)
# age height sex
# alice 12 130 f
# bob 13 140 m
# sonia 16 165 f
# unstack 可以將一個 DataFrame 變成一個多重索引的 Series
s = p.unstack()
print(s)
# age alice 12
# bob 13
# sonia 16
# height alice 130
# bob 140
# sonia 165
# sex alice f
# bob m
# sonia f
# dtype: object
print(s.index)
# MultiIndex([( 'age', 'alice'),
# ( 'age', 'bob'),
# ( 'age', 'sonia'),
# ('height', 'alice'),
# ('height', 'bob'),
# ('height', 'sonia'),
# ( 'sex', 'alice'),
# ( 'sex', 'bob'),
# ( 'sex', 'sonia')],
# )
# 所謂 MultiIndex 其實就是把 tuple 作為 index
# -------------------------------------------------------------
# 可以從 list of list 中創(chuàng)建 MultiIndex
names = ['alice']*3+['bob']*3
years = [2014, 2015, 2016]*2
ages = [40, 42, 45, 38, 40, 40]
s_list = pd.Series(ages, index=[names, years])
print(s_list)
# alice 2014 40
# 2015 42
# 2016 45
# bob 2014 38
# 2015 40
# 2016 40
# dtype: int64
# ---------------------------------------------------------------
# 也可以從鍵是 tuple 的字典中創(chuàng)建 MultiIndex 數(shù)據(jù)
s_tuple = pd.Series({('alice', 2014): 40,
('alice', 2015): 42,
('alice', 2016): 45,
('bob', 2014): 38,
('bob', 2015): 40,
('bob', 2016): 40})
print(s_tuple)
# 結(jié)果和上小節(jié)相同
# -----------------------------------------------------------------
# 還可以用 from_product 的方式創(chuàng)建 MultiIndex
# from_product 生成了兩組 index 的所有組合
name = ['alice', 'bob']
year = [2014, 2015, 2016]
i = pd.MultiIndex.from_product([name, year])
s = pd.Series([40, 42, 45, 38, 40, 40], index=i)
print(s)
# 結(jié)果和上上小節(jié)相同
# ----------------------------------------------------------------
# 為 MultiIndex 命名
i = pd.MultiIndex.from_product([name, year], names=['name', 'year'])
s = pd.Series([40, 42, 45, 38, 40, 40], index=i)
print(s)
# name year
# alice 2014 40
# 2015 42
# 2016 45
# bob 2014 38
# 2015 40
# 2016 40
# dtype: int64
# --------------------------------------------------------------------
# 改變 MultiIndex 的名字
s.index.names = ['Names', 'YEARS']
MultiIndex DataFrame 利用 from_product 可以方便地創(chuàng)建 MultiIndex。
index = pd.MultiIndex.from_product([[2013, 2014],
[1, 2, 3]],
names=['year',
'visit'])
columns = pd.MultiIndex.from_product([['Bob', 'Sue'],
['avant', 'arrière']],
names=['client',
'pression'])
# on crée des pressions de pneus factices
data = 2 + np.random.rand(6, 4)
# on crée la DataFrame
mecanics_data = pd.DataFrame(data, index=index, columns=columns)
print(mecanics_data)
# client Bob Sue
# pression avant arrière avant arrière
# year visit
# 2013 1 2.991843 2.795090 2.388053 2.243434
# 2 2.166630 2.491427 2.506627 2.657610
# 3 2.624870 2.897621 2.094361 2.656947
# 2014 1 2.823338 2.448971 2.155188 2.768642
# 2 2.094320 2.904009 2.141616 2.181879
# 3 2.880923 2.403404 2.916429 2.694188
MultiIndex DataFrame 的索引十分方便
mecanics_data.loc[2013, 'bob']
# pression avant arrière
# visit
# 1 2.991843 2.795090
# 2 2.166630 2.491427
# 3 2.624870 2.897621
# 為了獲取低級索引的數(shù)據(jù),用到元組:
mecanics_data.loc[(2013, 2), ('Bob', 'avant')]
# 2.166630
元組配合 slice,為 MultiIndex DataFrame 做切片:
print(mecanics_data.loc[slice((2013, 2), (2014, 1)), ('Sue', slice(None))])
# client Sue
# pression avant arrière
# year visit
# 2013 2 2.506627 2.657610
# 3 2.094361 2.656947
# 2014 1 2.155188 2.768642
# ------------------------------------------------------------------
# 對需要全部保留的維度,可以使用 ":",或者 slice(None)
print(mecanics_data.loc[(slice(None), slice(1, 2)), :])
# print(mecanics_data.loc[(slice(None), slice(1, 2)), slice(None)])
# client Bob Sue
# pression avant arrière avant arrière
# year visit
# 2013 1 2.991843 2.795090 2.388053 2.243434
# 2 2.166630 2.491427 2.506627 2.657610
# 2014 1 2.823338 2.448971 2.155188 2.768642
# 2 2.094320 2.904009 2.141616 2.181879
# ------------------------------------------------------------------
# 除了 slice 還可以用 pd.IndexSlice
idx = pd.IndexSlice
# 此處 idx第一個維度為一級索引,第二個維度為二級索引,
# 相當(dāng)于 tuple 的第一個元素和第二個元素
print(mecanics_data.loc[idx[:, 1:2], idx['Sue', :]])
# client Sue
# pression avant arrière
# year visit
# 2013 1 2.388053 2.243434
# 2 2.506627 2.657610
# 2014 1 2.155188 2.768642
# 2 2.141616 2.181879
高級操作
concat 用于將兩個表拼接起來,它適用于兩個表有相同的 index 或者有相同的 columns。
df1 = pd.DataFrame(np.random.randint(1, 10, size=(2, 2)),
columns=list('ab'), index=list('xy'))
df2 = pd.DataFrame(np.random.randint(1, 10, size=(2, 2)),
columns=list('cd'), index=list('xy'))
print(df1)
# a b
# x 4 8
# y 1 7
print(df2)
# c d
# x 1 4
# y 3 3
print(pd.concat((df1, df2), axis=1))
# a b c d
# x 4 8 1 4
# y 1 7 3 3
concat 也適用于拼接 Series,但是不論是 DataFrame 還是 Series,它不會檢查各行的 index 是否重復(fù)。
s1 = pd.Series([30, 35], index=['alice', 'bob'])
s2 = pd.Series([32, 22, 29], index=['bill', 'alice', 'jo'])
pd.concat([s1, s2])
# alice 30
# bob 35
# bill 32
# alice 22
# jo 29
# dtype: int64
# 上面的拼接結(jié)果中有兩個 alice
一個解決方案是設(shè)置 verify_integrity 參數(shù),它會在遇到兩個相同 index 的時候報錯。但是這無疑會導(dǎo)致額外的計算,因此除非確實必要,一般不設(shè)置它。
try:
pd.concat([s1, s2], verify_integrity=True)
except ValueError as e:
print(f"erreur de concaténation:\n{e}")
# erreur de concaténation:
# Indexes have overlapping values: Index(['alice'], dtype='object')
設(shè)置拼接參數(shù)
p1 = pd.DataFrame(np.random.randint(1, 10, size=(2,2)),
columns=list('ab'), index=list('xy'))
# a b
# x 8 9
# y 7 5
p2 = pd.DataFrame(np.random.randint(1, 10, size=(2,2)),
columns=list('ab'), index=list('zt'))
# c d
# x 2 3
# y 8 3
# -------------------------------------------------------
# 如果將它們按行拼接,則會出現(xiàn) NaN:
print(pd.concat((p1, p2)))
# a b c d
# x 8.0 9.0 NaN NaN
# y 7.0 5.0 NaN NaN
# x NaN NaN 2.0 3.0
# y NaN NaN 8.0 3.0
# -------------------------------------------------------
# 設(shè)置 join 參數(shù)
print(pd.concat([p1, p2]), join='inner') # 只保留兩表的交集
# Empty DataFrame
# Columns: []
# Index: [x, y, x, y]
# -------------------------------------------------------
# 調(diào)用 reindex,保留我們想要的行
pd.concat([p1, p2], axis=1).reindex(['x'])
# a b c d
# x 8 9 2 3
# 如要保留所想要的列,只需要在 reindex 中傳入 axis=1 即可
pd.concat([p1, p2], axis=1).reindex(p2.columns, axis=1)
# c d
# x 2 3
# y 8 3
merge 適用于兩個表某列相同,然后所有的融合都基于該列:
df1 = pd.DataFrame({'personnel':['Bob', 'Lisa', 'Sue'], 'groupe':['SAF', 'R&D', 'RH']})
df2 = pd.DataFrame({'personnel':['Lisa', 'Bob', 'Sue'], 'date embauche':[2004, 2008, 2014]})
print(df1)
# personnel groupe
# 0 Bob SAF
# 1 Lisa R&D
# 2 Sue RH
print(df2)
# personnel date embauche
# 0 Lisa 2004
# 1 Bob 2008
# 2 Sue 2014
# 上面兩個表中,personnel 列是相同的,因此,merge 時會按照 personnel 列對齊。
print(pd.merge(df1, df2))
# personnel groupe date embauche
# 0 Bob SAF 2008
# 1 Lisa R&D 2004
# 2 Sue RH 2014
merge 默認采取 inner join 的策略,如果以某列為基準(zhǔn),那么最終結(jié)果中,只有同時出現(xiàn)在這兩列中的數(shù)據(jù)被保留。
總共有三種merge 的方式:
- one-to-one, 即在兩表中的各基準(zhǔn)列內(nèi)部,不存在重復(fù)的數(shù)據(jù),是一一對應(yīng)的關(guān)系
- many-to-one,其中一個表的基準(zhǔn)列中,存在重復(fù)數(shù)據(jù),而另一個表中基準(zhǔn)列內(nèi)部元素都是獨一無二的,因此,merge 時只需要把另一個表中的數(shù)據(jù)復(fù)制 n 份即可。
- many-to-many,兩個表中基準(zhǔn)列內(nèi)部數(shù)據(jù)都有重復(fù),那么就對各行做笛卡爾乘積。
# ---------------------many-to-one---------------------
df1 = pd.DataFrame({'patient': ['Bob', 'Lisa', 'Sue'],
'repas': ['SS', 'SS', 'SSR']})
# patient repas
# 0 Bob SS
# 1 Lisa SS
# 2 Sue SSR
df2 = pd.DataFrame({'repas': ['SS', 'SSR'],
'explication': ['sans sel', 'sans sucre']})
# repas explication
# 0 SS sans sel
# 1 SSR sans sucre
# df1 和 df2 以 repas 為基準(zhǔn)融合,而 df1 中的 repas 列有重復(fù)元素,
# 因此是 one-to-many 的問題,融合后結(jié)果如下:
print(pd.merge(df1, df2))
# patient repas explication
# 0 Bob SS sans sel
# 1 Lisa SS sans sel
# 2 Sue SSR sans sucre
# ---------------------many-to-many---------------------
df1 = pd.DataFrame({'patient': ['Bob', 'Lisa', 'Sue'],
'repas': ['SS', 'SS', 'SSR']})
# patient repas
# 0 Bob SS
# 1 Lisa SS
# 2 Sue SSR
df2 = pd.DataFrame({'repas': ['SS', 'SS', 'SSR'],
'explication': ['sans sel', 'légumes', 'sans sucre']})
# repas explication
# 0 SS sans sel
# 1 SS légumes
# 2 SSR sans sucre
# 此時 df2 中 repas 列也有重復(fù)元素,依據(jù) many-to-many 的策略,融合結(jié)果如下
print(pd.merge(df1, df2))
# patient repas explication
# 0 Bob SS sans sel
# 1 Bob SS légumes
# 2 Lisa SS sans sel
# 3 Lisa SS légumes
# 4 Sue SSR sans sucre
# 本質(zhì)上就是不停地重復(fù),遍歷所有可能。
merge 可以設(shè)置 on= 或者 left_on=、right_on= 顯式指定基準(zhǔn)列
# ------------------------------ on -------------------------------------
df1 = pd.DataFrame({'employee': ['Bob', 'Lisa', 'Sue'],
'group': ['Accounting', 'Engineering', 'HR']})
df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Sue'],
'hire_date': [2004, 2008, 2014]})
pd.merge(df1, df2, on='employee') # employee 出現(xiàn)在兩個表中
# ------------------------------left\right on-------------------------------------
df1 = pd.DataFrame({'employee': ['Bob', 'Lisa', 'Sue'],
'group': ['Accounting', 'Engineering', 'HR']})
df2 = pd.DataFrame({'name': ['Lisa', 'Bob', 'Sue'],
'hire_date': [2004, 2008, 2014]})
m = pd.merge(df1, df2, left_on='employee', right_on='name')
# 此時需要注意的問題是,left_on 和 right_on 的列都會被保留,但實際上這兩列是重復(fù)的。
# employee group name hire_date
# 0 Bob Accounting Bob 2008
# 1 Lisa Engineering Lisa 2004
# 2 Sue HR Sue 2014
# 可以用
m.drop('name', axis=1) # 刪掉重復(fù)列,它會返回一個新的 DataFrame
# 設(shè)置 inplace=True,在原 DataFrame 上做修改
m.drop('name', axis=1, inplace=True)
當(dāng)兩表中的基準(zhǔn)列元素不完全一致時,通過設(shè)置 how 有四種 merge 策略:
df1 = pd.DataFrame({'name': ['Bob', 'Lisa', 'Sue'],
'pulse': [70, 63, 81]})
# name pulse
# 0 Bob 70
# 1 Lisa 63
# 2 Sue 81
df2 = pd.DataFrame({'name': ['Eric', 'Bob', 'Marc'],
'weight': [60, 100, 70]})
# name weight
# 0 Eric 60
# 1 Bob 100
# 2 Marc 70
# 上述兩表以name為基準(zhǔn)列,但基準(zhǔn)列中只有 Bob 出現(xiàn)在兩個表內(nèi)。
# ------------------------默認情況下,how='inner'------------------------
# 只保留同時出現(xiàn)在兩個表中的數(shù)據(jù)
print(pd.merge(df1, df2, how='inner'))
# name pulse weight
# 0 Bob 70 100
# ------------------------ how=''outer" ------------------------
# 保留全部條目,缺失數(shù)據(jù)用 NaN 填充
print(pd.merge(df1, df2, how='outer'))
# name pulse weight
# 0 Bob 70.0 100.0
# 1 Lisa 63.0 NaN
# 2 Sue 81.0 NaN
# 3 Eric NaN 60.0
# 4 Marc NaN 70.0
# ------------------------ how=''left" ------------------------
# 只保留 df1 的條目
print(pd.merge(df1, df2, how='left'))
# name pulse weight
# 0 Bob 70 100.0
# 1 Lisa 63 NaN
# 2 Sue 81 NaN
# ------------------------ how=''right" ------------------------
和 how='left' 同理
groupby 按照某個指標(biāo)聚類,分別計算各類數(shù)據(jù)
import seaborn as sns
# 加載 titanic 數(shù)據(jù)
ti = sns.load_dataset('titanic').loc[:, ['survived', 'sex', 'class']]
# 計算平均生存率
ti.loc[:, 'survived'].mean()
# 0.38384
# 計算某個等級的生存率
ti.loc[ti.loc[:, 'class']=='Second', 'survived'].mean()
# 0.4728
# 將所有類別的數(shù)據(jù)綜合起來,一起計算(而不是每次設(shè)置條件做篩選)
# 例如,計算不同艙位等級的生存率
print(ti.groupby('class').mean())
# survived
# class
# First 0.629630
# Second 0.472826
# Third 0.242363
# 設(shè)置 as_index=False:
print(ti.groupby('class', as_index=False).mean())
# class survived
# 0 First 0.629630
# 1 Second 0.472826
# 2 Third 0.242363
# 按照多個指標(biāo)分類,計算各類數(shù)據(jù)
# 例如,同時按照艙位等級和性別分類,計算平均生存率
g = ti.groupby(['class', 'sex']).mean()
print(g)
# survived
# class sex
# First female 0.968085
# male 0.368852
# Second female 0.921053
# male 0.157407
# Third female 0.500000
# male 0.135447
g.index # 返回一個 MultiIndex
# MultiIndex([( 'First', 'female'),
# ( 'First', 'male'),
# ('Second', 'female'),
# ('Second', 'male'),
# ( 'Third', 'female'),
# ( 'Third', 'male')],
# names=['class', 'sex'])
groupby 返回值的屬性:
# -------------------------------------groups------------------------------------
d = pd.DataFrame({'key': list('ABCABC'), 'val': range(6)})
# key val
# 0 A 0
# 1 B 1
# 2 C 2
# 3 A 3
# 4 B 4
# 5 C 5
g = d.groupby('key')
# <pandas.core.groupby.generic.DataFrameGroupBy object at 0x7f3967995280>
g.groups # groups 屬性
# {'A': [0, 3], 'B': [1, 4], 'C': [2, 5]}
# .groups 返回一個字典,鍵為組名,值為該組各條數(shù)據(jù)所在的行下標(biāo)
# ------------------------ get_group('A') -------------------------
d = pd.DataFrame({'key': list("ABCABC"),
'val1': range(6), 'val2': range(100, 106)})
# key val1 val2
# 0 A 0 100
# 1 B 1 101
# 2 C 2 102
# 3 A 3 103
# 4 B 4 104
# 5 C 5 105
g = d.groupby('key')
g.groups # {'A': [0, 3], 'B': [1, 4], 'C': [2, 5]}
g.get_group('A')
# key val1 val2
# 0 A 0 100
# 3 A 3 103
# --------------------------.sum()['val2'] ----------------------------
g.sum()['val2']
# key
# A 203
# B 205
# C 207
# Name: val2, dtype: int64
# 同 g['val2'].sum()
# ---------------------------- for (name, dataframe) in g ----------------------
import seaborn as sns
tips = sns.load_dataset('tips')
g = tips.groupby('day')
for (group, index) in g:
print(f"On {group} the mean tip is {index['tip'].mean():.3}")
groupby 方法分發(fā)。groupby 返回的對象如果沒有實現(xiàn)某個 DataFrame的方法,該對象仍然可以調(diào)用,只不過是遍歷每個類別,分別調(diào)用。
g = tips.groupby('day')['total_bill']
g.discribe()
# count mean std min 25% 50% 75% max
# day
# Thur 62.00 17.68 7.89 7.51 12.44 16.20 20.16 43.11
# Fri 19.00 17.15 8.30 5.75 12.09 15.38 21.75 40.17
# Sat 87.00 20.44 9.48 3.07 13.91 18.24 24.74 50.81
# Sun 76.00 21.41 8.83 7.25 14.99 19.63 25.60 48.17
groupby().agg 方法:agg 中以 list/dict 形式傳入函數(shù)名(或名字的字符串),計算每個組的統(tǒng)計量。
tips.groupby('day').agg(['mean', 'std'])
# tips.groupby('day').agg([np.mean, np.std])
# total_bill tip size
# mean std mean std mean std
# day
# Thur 17.68 7.89 2.77 1.24 2.45 1.07
# Fri 17.15 8.30 2.73 1.02 2.11 0.57
# Sat 20.44 9.48 2.99 1.63 2.52 0.82
# Sun 21.41 8.83 3.26 1.23 2.84 1.01
# 也可以以字典形式,為每個 column 設(shè)置 agg 函數(shù)
tips.groupby('day').agg({'tip': np.mean, 'total_bill': np.std})
groupby().filter() filter 內(nèi)傳入篩選條件,可以是 lambda 表達式
d = pd.DataFrame({'key': list('ABCABC'),
'val1': range(6),
'val2' : range(100, 106)})
d_sub = d.groupby('key').filter(lambda x: x['val1'].sum()>3)
groupby().transform() transform 內(nèi)傳入變換函數(shù),如 lambda 表達式,變換函數(shù)將施加在每個子 group 上,一個經(jīng)典用例是用它來對每個 group 內(nèi)部中心化,或者用group 均值代替其中的 NaN。
r = np.random.normal(0.5, 2, 4)
d = pd.DataFrame({'key': list('ab'*2), 'data': r,'data2': r*2})
g = d.groupby('key')
g.transform(lambda x: x-x.mean())
以 titanic 的例子,我們希望得到這樣的表格:有三行,每行代表一個艙位級別;有兩列,每列代表一個性別。此時需要用到 pivot_table。pivot_table 相當(dāng)于把 groupby 的結(jié)果表示為二維表格。
g = ti.pivot_table('survived', # survived 是需要處理的列
aggfunc = np.mean, # 需要對數(shù)據(jù)執(zhí)行的操作
index = 'class', # 行
columns = 'sex') # 設(shè)置列
print(g)
# sex female male
# class
# First 0.968085 0.368852
# Second 0.921053 0.157407
# Third 0.500000 0.135447
日期和時間序列
numpy 和 pandas 可以很好地處理各種格式的時間字符串,將其轉(zhuǎn)化為標(biāo)準(zhǔn)格式。同時提供了一系列方法,對時間序列求區(qū)間、采樣等等。
import numpy as np
import pandas as pd
# --------------------------- numpy ---------------------------
# 設(shè)置日期
np.datetime64('2018-06-30')
# numpy.datetime64('2018-06-30')
# 設(shè)置日期和時間
np.datetime64('2018-06-30 08:35:23')
# numpy.datetime64('2018-06-30T08:35:23')
# 求兩個時間點之差
np.datetime64('2018-06-30 08:35:23')-np.datetime('2018-06-20 08:37:23')
# numpy.timedelta64(863880,'s')
# --------------------------- pandas -----------------------------
# 傳入一個時間字符串
pd.to_datetime('10 june 1973 8h30')
# Timestamp('1973-06-10 08:30:00')
# 傳入時間 list
pd.to_datetime(['10 june 1973 8h30', '22-JUNE-1973'])
# DatetimeIndex(['1973-06-10 08:30:00', '1973-06-22 00:00:00'], dtype='datetime64[ns]', freq=None)
# ----------------------- 生成時間序列 -------------------------------
index = pd.date_range('1 jan 2018', periods=10, freq='D') # D 表示以天為單位
# 除了D 還可以用 W(week) M(month)
# DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
# '2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08',
# '2018-01-09', '2018-01-10'],
# dtype='datetime64[ns]', freq='D')
index = pd.date_range('1 jan 2018', periods=1000, freq='43h36t') # freq 也可以寫成 43h36min
# ------------------- 將時間序列作為index -------------------
s = pd.Series(np.random.randint(100, size=1000), index=index)
s.head()
# 2018-01-01 00:00:00 13
# 2018-01-02 19:36:00 46
# 2018-01-04 15:12:00 23
# 2018-01-06 10:48:00 55
# 2018-01-08 06:24:00 60
# Freq: 2616T, dtype: int32
# ---------------------- 索引 --------------------------------
s['2018'] # 把2018年所有時間篩選出來
s['dec 2018'] # 也可以是 'Dec 2018' '12 2018' '2018-12' 等等,pandas 支持各種格式的時間字符串
#------------------- 切片 ------------------------------------
s['dec 2018': '3 jan 2019']
# -------------------- resample ---------------------------
# resample 是按照一定的規(guī)則將時間序列分組
s.resample('W-WED').mean() # 以每周三 W(week)-WED(wednesday) 為節(jié)點分組
# ----------------------- 處理錯誤 --------------------------
date = '100/06/2018'
# 默認會報錯
pd.to_datetime(date)
# ValueError: ('Unknown string format:', '100/06/2018')
# 出現(xiàn)錯誤時返回輸入的字符串
pd.to_datetime(date, errors='ignore')
# '100/06/2018'
# 將錯誤標(biāo)記為 NaT (Not a time, 后續(xù)按 NaN 處理)
pd.to_datetime(data, errors='coerce')
# NaT
# 下例中最后一項為 NaT
d = pd.to_datetime(['jun 2018', '10/12/1980',
'25 january 2000', '100 june 1900'],
errors='coerce')
print(d)
# DatetimeIndex(['2018-06-01', '1980-10-12', '2000-01-25', 'NaT'], dtype='datetime64[ns]', freq=None)
# 可以用處理 NaN 的方法來處理 NaT
d.fillna(pd.to_datetime('10 june 1980'))
# DatetimeIndex(['2018-06-01', '1980-10-12', '2000-01-25', '1980-06-10'], dtype='datetime64[ns]', freq=None)