Pandas 入门

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 和字典一样,索引和修改的时间复杂度为 O(1)。它有两种索引方式,第一种索引方式与字典相同,但强烈不推荐,会导致各种副作用:

# 下面这种方法强烈不推荐
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

  1. 可以通过多个 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

  1. 还可以通过 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
  1. 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
  1. 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
  1. 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)

字符串操作

  • 字符串方法只适用于 SeriesIndex,不适用于 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,非缺失部分为 Falseisnull() 是它的 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_tablepivot_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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359

推荐阅读更多精彩内容

  • pandas Pandas是线上服务类型,数据分析和数据处理(在机器学习中数据处理) 数据分析三剑客: numpy...
    Galaxy_saturn阅读 770评论 0 1
  • pandas入门 简介 pandas包含的数据结构和操作工具能快速简单地清洗和分析数据。 pandas经常与Num...
    python测试开发阅读 2,394评论 1 16
  • 功能包括但不限于: 1.按轴自动或显式数据对齐功能的数据结构2.集成时间序列功能3.数学运算和约简4.灵活处理缺失...
    Shinichi新一君阅读 689评论 0 1
  • 5.3汇总和计算描述性统计 pandas对象拥有一组常用的数学和统计方法。他们大部分都属于约简和汇总统计,用于从S...
    凌岸_ing阅读 1,687评论 0 1
  • 目录 1.创建对象 2.查看数据 3.写入数据(read_csv) 4.写出/导出数据(to_csv) 5.读取行...
    鲸鱼酱375阅读 1,117评论 0 2