Series 和 index 简介
创建 Series
pd.Series
主要设置两个参数,data 和 index,如果不设置 index,则 index 从 0 开始递增。除此之外,还可以设置 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)
除了直接设置各个字段,还可以将字典作为参数传入,series 会自动将 key 作为 index,将 value 作为 data。
d = {k:v**2 for k, v in zip('abcdefghij', range(10))}
s = pd.Series(d, dtype='int64')
为了保证数据存取的效率,series 的 index 必须是可哈希的。
索引
series 和字典一样,索引和修改的时间复杂度为 。它有两种索引方式,第一种索引方式与字典相同,但强烈不推荐,会导致各种副作用:
# 下面这种方法强烈不推荐
s['Eve']
# 20
s['Eve':'Liz']
# Eve 20
# Bill 30
# Liz 40
# dtype: int64
在某些情况下,它会产生歧义:
s = pd.Series(['a', 'b', 'c'], index=[2, 0, 1])
print(s[0])
# 'b'
print(s[0:1])
# 'a'
# 即,当做切片时,是位置优先的,当获取某个元素时,是 label 优先的
# 为了解决这种歧义,pandas 引入了 loc 和 iloc 方法,前者索引的是 label,后者是位置下标。
第二种索引方式是采用 .loc
方法,这种写法是推荐的:
s.loc[['Eve']
# 20
s.loc['Eve':'Liz']
# Eve 20
# Bill 30
# Liz 40
# dtype: int64
注意一个细节:利用 index 做切片时,和 list 或者 array 利用下标切片,不包括末尾元素不同,index 切片是包括 end 元素的。
除了利用 index 切片,Series 也可以用下标切片,此时不包括终点下标对应的元素,表现和 python 惯例一致:
s.iloc[0] # 等价于 s.loc['Eve']
s.iloc[0:3]
# Eve 20
# Bill 30
# Liz 40
# dtype: int64
不像字典的 key 是唯一的,Series 支持 index 包含重复元素。但对 Series 做切片时,如果重复的 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 设置 key 参数:
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 的数据会相加,只存在于其中一个 Series 的数据相加后为 NaN,但也可以指定一方缺失的 label 对应的默认值:
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
数据类型
需要注意的是,在操作过程中, series value 的数据类型可能会隐式地被改变,如果不注意,很有可能影响增删的效率,甚至产生错误的结果。
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] # 增加了一条数据,又删除,导致了数据虽然没变,但是数据类型改变了
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)
产生错误结果的例子:
s = pd.Series([1,2,3])
s[3] = '4'
print(s+s)
# 0 2
# 1 4
# 2 6
# 3 44
# dtype: object
DataFrame 简介
创建 DataFrame
- 可以通过多个 Series 创建 DataFrame:
age = pd.Series([30, 20, 50], index = ['alice', 'bob', 'julie])
height = pd.Series([150, 170, 168], index=['alice', 'marc', 'julie'])
# 将字典作为参数传入 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 将缺失的值设为 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 创建 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,并创建新的 index,以数字为编号:
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)
查看元素
包括一系列函数:
import seaborn as sns
tips = sns.load_dataset('tips') # 加载一个现成的DataFrame
tips.head(n=2) # 展示前两行
tips.tail(n=3) # 展示后三行
# 也可以不设置 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()
默认只显示数字列,但也可以设置参数 include='all'
现实所有列。
p.loc['sonia']
展示 sonia 行
p.loc['sonia', 'age']
只显示 sonia 的年龄
根据条件筛选元素
筛选出女性条目:
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 则优先返回 attribute
p.age = 3 # 设置与 age 列同名的 attribute,那么下次调用 p.age 时,优先返回 3
# 再比如,为 p 增加一列 mean
p['mean'] = 1
# 但是,由于 DataFrame 本身有 mean,那么 p.mean 并不会返回 mean 这列
p.drop(columns=['mean', ], inplace=True)
用于删除一列或多列,inplace
作用是,设置是否修改原来的 p,如果True,返回 None,原 p 被修改,如果 False,返回被修改后的 DataFrame,同时原 p 保留。
Universal functions and pandas
DataFrame 支持所有 numpy 的函数,numpy 函数可以直接施加在 DataFrame 上,例如:
d = pd.DataFrame(np.random.rand(3,3), columns=list('abc'))
np.log(d) # 直接对 values 取 log,同时保留 行、列的label
但是,如果需要用到 DataFrame 的 label 对齐特性,例如两个 index 顺序并不相同的 DataFrame 相加,那么 numpy 的函数将直接计算中间的 values,而不会考虑它们 label 对齐的问题。(该问题已经在 pandas 0.2.5 中被修正)
运算中设置 fill_value
可以让表中缺失的数据被 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
# 不设置 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
# 设置后
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
# 我们发现,如果一个值在两个表中都是 NaN,那么这个值仍旧是 NaN
当一个 Series 和一个 DataFrame 相加时,pandas 会默认 Series 是一行,并把它广播到其它行。Series 的 index 会被对应到 DataFrame 的列上,并对齐。如果 Series 的 index 与 DataFrame 的列没关系,那么会扩增 DataFrame,扩增区域对应的数据为 NaN。如果想让 Series 的 index 和 DataFrame 的index 对应,则需要指定 axis=0:
dataframe.add(series_col, axis=0)
字符串操作
- 字符串方法只适用于
Series
和Index
,不适用于DataFrame
- 字符串方法不改变
NaN
,同时,把所有非字符串元素转为NaN
- 字符串方法返回一个
Series
或者Index
的拷贝,不改变输入的对象 - 大多数 Python
str
类方法在 pandas 中以向量化的形式存在 - 调用字符串方法的通用句法为:
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
# 所有字符串都变成了小写,此外其中的非字符串元素被转换为了 NaN,而原本的 NaN 没变
# 删掉字符串前后多余的空格
a = a.str.strip()
print(a)
# 0 alice
# 1 bob
# 2 marc
# 3 bill
# 4 NaN
# 5 julie
# 6 NaN
# dtype: object
处理缺失数据
在构建 pandas Series 或者 DataFrame 时,有两种方式可以表示 NaN,一种是 np.NaN
,另一种是python 的 None
对象。np.NaN
的数据类型是 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 的数据变成了 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 利用如下方法处理缺失数据:
-
isna()
返回一个 mask,在缺失数据部分为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: []
# ---------------------------------------
# 设置 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 可以设置该行/列至少包含几个 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 后面一行的数据填充 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 前面一列的数据填充 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
适用于数组大于二维的情况。所有可以用 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 中创建 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 的字典中创建 MultiIndex 数据
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)
# 结果和上小节相同
# -----------------------------------------------------------------
# 还可以用 from_product 的方式创建 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)
# 结果和上上小节相同
# ----------------------------------------------------------------
# 为 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
可以方便地创建 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
# 为了获取低级索引的数据,用到元组:
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第一个维度为一级索引,第二个维度为二级索引,
# 相当于 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 是否重复。
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
# 上面的拼接结果中有两个 alice
一个解决方案是设置 verify_integrity
参数,它会在遇到两个相同 index 的时候报错。但是这无疑会导致额外的计算,因此除非确实必要,一般不设置它。
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')
设置拼接参数
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
# -------------------------------------------------------
# 如果将它们按行拼接,则会出现 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
# -------------------------------------------------------
# 设置 join 参数
print(pd.concat([p1, p2]), join='inner') # 只保留两表的交集
# Empty DataFrame
# Columns: []
# Index: [x, y, x, y]
# -------------------------------------------------------
# 调用 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 的策略,如果以某列为基准,那么最终结果中,只有同时出现在这两列中的数据被保留。
总共有三种merge 的方式:
- one-to-one, 即在两表中的各基准列内部,不存在重复的数据,是一一对应的关系
- many-to-one,其中一个表的基准列中,存在重复数据,而另一个表中基准列内部元素都是独一无二的,因此,merge 时只需要把另一个表中的数据复制 n 份即可。
- many-to-many,两个表中基准列内部数据都有重复,那么就对各行做笛卡尔乘积。
# ---------------------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 为基准融合,而 df1 中的 repas 列有重复元素,
# 因此是 one-to-many 的问题,融合后结果如下:
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 列也有重复元素,依据 many-to-many 的策略,融合结果如下
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
# 本质上就是不停地重复,遍历所有可能。
merge
可以设置 on=
或者 left_on=
、right_on=
显式指定基准列
# ------------------------------ 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 出现在两个表中
# ------------------------------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 的列都会被保留,但实际上这两列是重复的。
# 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) # 删掉重复列,它会返回一个新的 DataFrame
# 设置 inplace=True,在原 DataFrame 上做修改
m.drop('name', axis=1, inplace=True)
当两表中的基准列元素不完全一致时,通过设置 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为基准列,但基准列中只有 Bob 出现在两个表内。
# ------------------------默认情况下,how='inner'------------------------
# 只保留同时出现在两个表中的数据
print(pd.merge(df1, df2, how='inner'))
# name pulse weight
# 0 Bob 70 100
# ------------------------ how=''outer" ------------------------
# 保留全部条目,缺失数据用 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
按照某个指标聚类,分别计算各类数据
import seaborn as sns
# 加载 titanic 数据
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
# 将所有类别的数据综合起来,一起计算(而不是每次设置条件做筛选)
# 例如,计算不同舱位等级的生存率
print(ti.groupby('class').mean())
# survived
# class
# First 0.629630
# Second 0.472826
# Third 0.242363
# 设置 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
# 按照多个指标分类,计算各类数据
# 例如,同时按照舱位等级和性别分类,计算平均生存率
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 返回一个字典,键为组名,值为该组各条数据所在的行下标
# ------------------------ 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
方法分发。groupby
返回的对象如果没有实现某个 DataFrame的方法,该对象仍然可以调用,只不过是遍历每个类别,分别调用。
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 形式传入函数名(或名字的字符串),计算每个组的统计量。
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 设置 agg 函数
tips.groupby('day').agg({'tip': np.mean, 'total_bill': np.std})
groupby().filter()
filter 内传入筛选条件,可以是 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 内传入变换函数,如 lambda 表达式,变换函数将施加在每个子 group 上,一个经典用例是用它来对每个 group 内部中心化,或者用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
相当于把 groupby
的结果表示为二维表格。
g = ti.pivot_table('survived', # survived 是需要处理的列
aggfunc = np.mean, # 需要对数据执行的操作
index = 'class', # 行
columns = 'sex') # 设置列
print(g)
# sex female male
# class
# First 0.968085 0.368852
# Second 0.921053 0.157407
# Third 0.500000 0.135447
日期和时间序列
numpy 和 pandas 可以很好地处理各种格式的时间字符串,将其转化为标准格式。同时提供了一系列方法,对时间序列求区间、采样等等。
import numpy as np
import pandas as pd
# --------------------------- numpy ---------------------------
# 设置日期
np.datetime64('2018-06-30')
# numpy.datetime64('2018-06-30')
# 设置日期和时间
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 是按照一定的规则将时间序列分组
s.resample('W-WED').mean() # 以每周三 W(week)-WED(wednesday) 为节点分组
# ----------------------- 处理错误 --------------------------
date = '100/06/2018'
# 默认会报错
pd.to_datetime(date)
# ValueError: ('Unknown string format:', '100/06/2018')
# 出现错误时返回输入的字符串
pd.to_datetime(date, errors='ignore')
# '100/06/2018'
# 将错误标记为 NaT (Not a time, 后续按 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)