Pandas基础

import pandas as pd
import numpy as np

查看Pandas版本

pd.__version__
'1.0.1'

文件读取与写入

读取

csv格式

df = pd.read_csv('data/table.csv')
df.head()

txt格式

df_txt = pd.read_table('data/table.txt') #可设置sep分隔符参数
df_txt

写入

csv格式

df.to_csv('data/new_table.csv')

基本数据结构

Series

创建一个Series

对于一个Series,其中最常用的属性为值(values),索引(index),名字(name),类型(dtype

s
a   -0.282809
b   -0.316346
c    0.535802
d    1.802756
e    1.808229
Name: 这是一个Series, dtype: float64

访问Series属性

s.values
array([-0.28280874, -0.31634646,  0.53580237,  1.802756  ,  1.8082295 ])
s.name
'这是一个Series'
s.index
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
s.dtype
dtype('float64')

取出某一个元素

s['a']
-0.2828087433467871

调用方法

s.mean()
0.7095265322328809

Series有相当多的方法可以调用:

print([attr for attr in dir(s) if not attr.startswith('_')])
['T', 'a', 'abs', 'add', 'add_prefix', 'add_suffix', 'agg', 'aggregate', 'align', 'all', 'any', 'append', 'apply', 'argmax', 'argmin', 'argsort', 'array', 'asfreq', 'asof', 'astype', 'at', 'at_time', 'attrs', 'autocorr', 'axes', 'b', 'between', 'between_time', 'bfill', 'bool', 'c', 'clip', 'combine', 'combine_first', 'convert_dtypes', 'copy', 'corr', 'count', 'cov', 'cummax', 'cummin', 'cumprod', 'cumsum', 'd', 'describe', 'diff', 'div', 'divide', 'divmod', 'dot', 'drop', 'drop_duplicates', 'droplevel', 'dropna', 'dtype', 'dtypes', 'duplicated', 'e', 'empty', 'eq', 'equals', 'ewm', 'expanding', 'explode', 'factorize', 'ffill', 'fillna', 'filter', 'first', 'first_valid_index', 'floordiv', 'ge', 'get', 'groupby', 'gt', 'hasnans', 'head', 'hist', 'iat', 'idxmax', 'idxmin', 'iloc', 'index', 'infer_objects', 'interpolate', 'is_monotonic', 'is_monotonic_decreasing', 'is_monotonic_increasing', 'is_unique', 'isin', 'isna', 'isnull', 'item', 'items', 'iteritems', 'keys', 'kurt', 'kurtosis', 'last', 'last_valid_index', 'le', 'loc', 'lt', 'mad', 'map', 'mask', 'max', 'mean', 'median', 'memory_usage', 'min', 'mod', 'mode', 'mul', 'multiply', 'name', 'nbytes', 'ndim', 'ne', 'nlargest', 'notna', 'notnull', 'nsmallest', 'nunique', 'pct_change', 'pipe', 'plot', 'pop', 'pow', 'prod', 'product', 'quantile', 'radd', 'rank', 'ravel', 'rdiv', 'rdivmod', 'reindex', 'reindex_like', 'rename', 'rename_axis', 'reorder_levels', 'repeat', 'replace', 'resample', 'reset_index', 'rfloordiv', 'rmod', 'rmul', 'rolling', 'round', 'rpow', 'rsub', 'rtruediv', 'sample', 'searchsorted', 'sem', 'set_axis', 'shape', 'shift', 'size', 'skew', 'slice_shift', 'sort_index', 'sort_values', 'squeeze', 'std', 'sub', 'subtract', 'sum', 'swapaxes', 'swaplevel', 'tail', 'take', 'to_clipboard', 'to_csv', 'to_dict', 'to_excel', 'to_frame', 'to_hdf', 'to_json', 'to_latex', 'to_list', 'to_markdown', 'to_numpy', 'to_period', 'to_pickle', 'to_sql', 'to_string', 'to_timestamp', 'to_xarray', 'transform', 'transpose', 'truediv', 'truncate', 'tshift', 'tz_convert', 'tz_localize', 'unique', 'unstack', 'update', 'value_counts', 'values', 'var', 'view', 'where', 'xs']

DataFrame

创建一个DataFrame

df = pd.DataFrame({'col1':list('abcde'),'col2':range(5,10),'col3':[1.3,2.5,3.6,4.6,5.8]},
                 index=list('一二三四五'))
df

DataFrame取出一列为Series

df['col1']
一    a
二    b
三    c
四    d
五    e
Name: col1, dtype: object
type(df)
pandas.core.frame.DataFrame
type(df['col1'])
pandas.core.series.Series

修改行或列名

df.rename(index={'一':'one'},columns={'col1':'new_col1'})

调用属性和方法

df.index
Index(['一', '二', '三', '四', '五'], dtype='object')
df.columns
Index(['col1', 'col2', 'col3'], dtype='object')
df.values
array([['a', 5, 1.3],
       ['b', 6, 2.5],
       ['c', 7, 3.6],
       ['d', 8, 4.6],
       ['e', 9, 5.8]], dtype=object)
df.shape
(5, 3)
df.mean() #本质上是一种Aggregation操作
col2    7.00
col3    3.56
dtype: float64

索引对齐特性

这是Pandas中非常强大的特性,不理解这一特性有时就会造成一些麻烦

df1 = pd.DataFrame({'A':[1,2,3]},index=[1,2,3])
df2 = pd.DataFrame({'A':[1,2,3]},index=[3,1,2])
df1-df2 #由于索引对齐,因此结果不是0

列的删除与添加

对于删除而言,可以使用drop函数或delpop

df.drop(index='五',columns='col1') #设置inplace=True后会直接在原DataFrame中改动
df['col1']=[1,2,3,4,5]
del df['col1']
df

pop方法直接在原来的DataFrame上操作,且返回被删除的列,与python中的pop函数类似

df['col1']=[1,2,3,4,5]
df.pop('col1')
一    1
二    2
三    3
四    4
五    5
Name: col1, dtype: int64
df
	col2	col3
一	5	1.3
二	6	2.5
三	7	3.6
四	8	4.6
五	9	5.8

可以直接增加新的列,也可以使用assign方法,但assign方法不会对原DataFrame做修改

df1['B']=list('abc')
df1
	A	B
1	1	a
2	2	b
3	3	c
df1.assign(C=pd.Series(list('def')))
	A	B	C
1	1	a	e
2	2	b	f
3	3	c	NaN

根据类型选择列

df.select_dtypes(include=['number']).head()
	col2	col3
一	5	1.3
二	6	2.5
三	7	3.6
四	8	4.6
五	9	5.8
df.select_dtypes(include=['float']).head()
	col3
一	1.3
二	2.5
三	3.6
四	4.6
五	5.8

Series转换为DataFrame

s = df.mean()
s.name='to_DataFrame'
s
col2    7.00
col3    3.56
Name: to_DataFrame, dtype: float64
s.to_frame()
	to_DataFrame
col2	7.00
col3	3.56

使用T符号可以转置

s.to_frame().T
	            col2	col3
to_DataFrame	7.0	    3.56

常用基本函数

从下面开始,包括后面所有章节,我们都会用到这份虚拟的数据集

df = pd.read_csv('data/table.csv')

headtail

df.head()
	School	Class	ID	Gender	Address	    Height	Weight	Math	Physics
0	S_1	    C_1	    1101	M	street_1	173	    63	    34.0	A+
1	S_1	    C_1	    1102	F	street_2	192	    73	    32.5	B+
2	S_1	    C_1	    1103	M	street_2	186	    82	    87.2	B+
3	S_1	    C_1	    1104	F	street_2	167	    81	    80.4	B-
4	S_1	    C_1	    1105	F	street_4	159	    64	    84.8	B+
df.tail()
	School	Class	ID	Gender	Address	    Height	Weight	Math	Physics
30	S_2	    C_4	    2401	F	street_2	192	    62	    45.3	A
31	S_2	    C_4	    2402	M	street_7	166	    82	    48.7	B
32	S_2	    C_4	    2403	F	street_6	158	    60	    59.7	B+
33	S_2	    C_4	    2404	F	street_2	160	    84	    67.7	B
34	S_2	    C_4	    2405	F	street_6	193	    54	    47.6	B

可以指定n参数显示多少行

df.head(3)
	School	Class	ID	Gender	Address	    Height	Weight	Math	Physics
0	S_1	    C_1	    1101	M	street_1	173	    63	    34.0	A+
1	S_1	    C_1	    1102	F	street_2	192	    73	    32.5	B+
2	S_1	    C_1	    1103	M	street_2	186	    82	    87.2	B+

uniquenunique

nunique显示有多少个唯一值

df['Physics'].nunique()
7

unique显示所有的唯一值

df['Physics'].unique()
array(['A+', 'B+', 'B-', 'A-', 'B', 'A', 'C'], dtype=object)

countvalue_counts

count返回非缺失值元素个数

df['Physics'].count()
35

value_counts返回每个元素有多少个

df['Physics'].value_counts()
B+    9
B     8
B-    6
A     4
A+    3
A-    3
C     2
Name: Physics, dtype: int64

describeinfo

info函数返回有哪些列、有多少非缺失值、每列的类型

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 35 entries, 0 to 34
Data columns (total 9 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   School   35 non-null     object 
 1   Class    35 non-null     object 
 2   ID       35 non-null     int64  
 3   Gender   35 non-null     object 
 4   Address  35 non-null     object 
 5   Height   35 non-null     int64  
 6   Weight   35 non-null     int64  
 7   Math     35 non-null     float64
 8   Physics  35 non-null     object 
dtypes: float64(1), int64(3), object(5)
memory usage: 2.6+ KB

describe默认统计数值型数据的各个统计量

df.describe()
        ID	    Height	    Weight	    Math
count 35.00000	35.000000	35.000000	35.000000
mean  1803.00000 174.142857	74.657143	61.351429
std	536.87741	13.541098	12.895377	19.915164
min	1101.00000	155.000000	53.000000	31.500000
25%	1204.50000	161.000000	63.000000	47.400000
50%	2103.00000	173.000000	74.000000	61.700000
75%	2301.50000	187.500000	82.000000	77.100000
max	2405.00000	195.000000	100.000000	97.000000

可以自行选择分位数

df.describe(percentiles=[.05, .25, .75, .95])
	        ID	  Height	  Weight	   Math
count 35.00000	35.000000	35.000000	35.000000
mean 1803.00000	174.142857	74.657143	61.351429
std	536.87741	13.541098	12.895377	19.915164
min	1101.00000	155.000000	53.000000	31.500000
5%	1102.70000	157.000000	56.100000	32.640000
25%	1204.50000	161.000000	63.000000	47.400000
50%	2103.00000	173.000000	74.000000	61.700000
75%	2301.50000	187.500000	82.000000	77.100000
95%	2403.30000	193.300000	97.600000	90.040000
max	2405.00000	195.000000	100.000000	97.000000

对于非数值型也可以用describe函数

df['Physics'].describe()
count     35
unique     7
top       B+
freq       9
Name: Physics, dtype: object

idxmaxnlargest

idxmax函数返回最大值,在某些情况下特别适用,idxmin功能类似

df['Math'].idxmax()
5

nlargest函数返回前几个大的元素值,nsmallest功能类似

df['Math'].nlargest(3)
5     97.0
28    95.5
11    87.7
Name: Math, dtype: float64

clipreplace

clipreplace是两类替换函数,clip是对超过或者低于某些值的数进行截断

df['Math'].head()
0    34.0
1    32.5
2    87.2
3    80.4
4    84.8
Name: Math, dtype: float64
df['Math'].clip(33,80).head()
Out[54]:
0    34.0
1    33.0
2    80.0
3    80.0
4    80.0
Name: Math, dtype: float64
df['Math'].mad()
16.924244897959188

replace是对某些值进行替换

df['Address'].head()
0    street_1
1    street_2
2    street_2
3    street_2
4    street_4
Name: Address, dtype: object
df['Address'].replace(['street_1','street_2'],['one','two']).head()
0         one
1         two
2         two
3         two
4    street_4
Name: Address, dtype: object

通过字典,可以直接在表中修改

df.replace({'Address':{'street_1':'one','street_2':'two'}}).head()
    School	Class	ID	Gender	Address	Height	Weight	Math	Physics
0	S_1	    C_1	    1101	M	one	    173	    63	    34.0	A+
1	S_1	    C_1	    1102	F	two	    192	    73	    32.5	B+
2	S_1	    C_1	    1103	M	two	    186	    82	    87.2	B+
3	S_1	    C_1	    1104	F	two	    167	    81	    80.4	B-
4	S_1	    C_1	    1105	F	street_4 159	64	    84.8	B+

apply函数

对于Series,它可以迭代每一列的值操作:

df['Math'].apply(lambda x:str(x)+'!').head() #可以使用lambda表达式,也可以使用函数
0    34.0!
1    32.5!
2    87.2!
3    80.4!
4    84.8!
Name: Math, dtype: object

对于DataFrame,它可以迭代每一个列操作:

df.apply(lambda x:x.apply(lambda x:str(x)+'!')).head() #这是一个稍显复杂的例子,有利于理解apply的功能
	School	Class	ID	Gender	Address	Height	Weight	Math	Physics
0	S_1!	C_1!	1101!	M!	street_1!	173!	63!	34.0!	A+!
1	S_1!	C_1!	1102!	F!	street_2!	192!	73!	32.5!	B+!
2	S_1!	C_1!	1103!	M!	street_2!	186!	82!	87.2!	B+!
3	S_1!	C_1!	1104!	F!	street_2!	167!	81!	80.4!	B-!
4	S_1!	C_1!	1105!	F!	street_4!	159!	64!	84.8!	B+!

排序

索引排序

df.set_index('Math').head() #set_index函数可以设置索引
        School	Class	ID	Gender	Address	    Height	Weight	Physics
Math								
34.0	S_1	    C_1	    1101	M	street_1	173	    63	    A+
32.5	S_1	    C_1	    1102	F	street_2	192	    73	    B+
87.2	S_1	    C_1	    1103	M	street_2	186	    82	    B+
80.4	S_1	    C_1	    1104	F	street_2	167	    81	    B-
84.8	S_1	    C_1	    1105	F	street_4	159	    64	    B+
df.set_index('Math').sort_index().head() #可以设置ascending参数,默认为升序,True
        School	Class	ID	Gender	Address	    Height	Weight	Physics
Math								
31.5	S_1	    C_3	    1301	M	street_4	161	    68	    B+
32.5	S_1 	C_1	    1102	F	street_2	192	    73	    B+
32.7	S_2	    C_3	    2302	M	street_5	171	    88	    A
33.8	S_1	    C_2	    1204	F	street_5	162	    63	    B
34.0	S_1	    C_1	    1101	M	street_1	173	    63	    A+

值排序

df.sort_values(by='Class').head()
School	Class	ID	Gender	Address	        Height	Weight	Math	Physics
0	    S_1	    C_1	1101	M	street_1	173	    63	    34.0	A+
19	    S_2	    C_1	2105	M	street_4	170	    81	    34.2	A
18	    S_2	    C_1	2104	F	street_5	159	    97	    72.2	B+
16	    S_2	    C_1	2102	F	street_6	161	    61	    50.6	B+
15	    S_2	    C_1	2101	M	street_7	174	    84	    83.3	C

多个值排序,即先对第一层排,在第一层相同的情况下对第二层排序

df.sort_values(by=['Address','Height']).head()
    School	Class	ID	Gender	Address	    Height	Weight	Math	Physics
0	S_1	    C_1	    1101	M	street_1	173	    63	    34.0	A+
11	S_1	    C_3	    1302	F	street_1	175	    57	    87.7	A-
23	S_2	    C_2	    2204	M	street_1	175	    74	    47.2	B-
33	S_2	    C_4	    2404	F	street_2	160	    84  	67.7	B
3	S_1	    C_1	    1104	F	street_2	167	    81	    80.4	B-

练习

练习一

现有一份关于美剧《权力的游戏》剧本的数据集,请解决以下问题:

pd.read_csv('data/Game_of_Thrones_Script.csv').head()
  • 在所有的数据中,一共出现了多少人物?
df['Name'].nunique()
564
  • 以单元格计数(即简单把一个单元格视作一句),谁说了最多的话?
df['Name'].value_counts().index[0]
'tyrion lannister'
  • 以单词计数,谁说了最多的单词?
    由于还没有学分组,因此方法繁琐
df_words = df.assign(Words=df['Sentence'].apply(lambda x:len(x.split()))).sort_values(by='Name')
df_words.head()
L_count = []
N_words = list(zip(df_words['Name'],df_words['Words']))
for i in N_words:
    if i == N_words[0]:
        L_count.append(i[1])
        last = i[0]
    else:
        L_count.append(L_count[-1]+i[1] if i[0]==last else i[1])
        last = i[0]
df_words['Count']=L_count
df_words['Name'][df_words['Count'].idxmax()]
'tyrion lannister'

练习二

现有一份关于科比的投篮数据集,请解决如下问题:

pd.read_csv('data/Kobe_data.csv',index_col='shot_id').head()
#index_col的作用是将某一列作为行索引
  • 哪种action_type和combined_shot_type的组合是最多的?
pd.Series(list(zip(df['action_type'],df['combined_shot_type']))).value_counts().index[0]
('Jump Shot', 'Jump Shot')
  • 在所有被记录的game_id中,遭遇到最多的opponent是一个支?
pd.Series(list(list(zip(*(pd.Series(list(zip(df['game_id'],df['opponent']))).unique()).tolist()))[1])).value_counts().index[0]
'SAS'

索引

import pandas as pd
df = pd.read_csv('data/table.csv',index_col='ID')
df.head()
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1101	S_1	    C_1	    M	    street_1	173	    63	    34.0	A+
1102	S_1	    C_1	    F	    street_2	192	    73	    32.5	B+
1103	S_1	    C_1	    M	    street_2	186	    82	    87.2	B+
1104	S_1	    C_1	    F	    street_2	167	    81	    80.4	B-
1105	S_1	    C_1	    F	    street_4	159	    64	    84.8	B+

单级索引

loc方法、iloc方法、[]操作符

最常用的索引方法可能就是这三类,其中iloc表示位置索引,loc表示标签索引,[]也具有很大的便利性,各有特点

loc方法

注意:所有在loc中使用的切片全部包含右端点!

单行索引
df.loc[1103]
School          S_1
Class           C_1
Gender            M
Address    street_2
Height          186
Weight           82
Math           87.2
Physics          B+
Name: 1103, dtype: object
多行索引
df.loc[[1102,2304]]
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1102	S_1	    C_1	    F	    street_2	192	    73	    32.5	B+
2304	S_2	    C_3	    F	    street_6	164	    81	    95.5	A-
df.loc[1304:].head()
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1304	S_1	    C_3	    M	    street_2	195	    70	    85.2	A
1305	S_1	    C_3	    F	    street_5	187	    69	    61.7	B-
2101	S_2	    C_1	    M	    street_7	174	    84	    83.3	C
2102	S_2	    C_1	    F	    street_6	161	    61	    50.6	B+
2103	S_2	    C_1	    M	    street_4	157	    61	    52.5	B-
df.loc[2402::-1].head()
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
2402	S_2	    C_4	    M	    street_7	166	    82	    48.7	B
2401	S_2	    C_4	    F	    street_2	192	    62	    45.3	A
2305	S_2	    C_3	    M	    street_4	187	    73	    48.9	B
2304	S_2	    C_3	    F	    street_6	164	    81	    95.5	A-
2303	S_2	    C_3	    F	    street_7	190	    99	    65.9	C
单列索引
df.loc[:,'Height'].head()
ID
1101    173
1102    192
1103    186
1104    167
1105    159
Name: Height, dtype: int64
多列索引:
df.loc[:,['Height','Math']].head()
     Height	Math
ID		
1101	173	34.0
1102	192	32.5
1103	186	87.2
1104	167	80.4
1105	159	84.8
df.loc[:,'Height':'Math'].head()
        Height	Weight	Math
ID			
1101	173	    63	    34.0
1102	192	    73	    32.5
1103	186	    82	    87.2
1104	167	    81	    80.4
1105	159	    64	    84.8
联合索引
df.loc[1102:2401:3,'Height':'Math'].head()
        Height	Weight	Math
ID			
1102	192	    73	    32.5
1105	159	    64	    84.8
1203	160	    53	    58.8
1301	161	    68	    31.5
1304	195	    70	    85.2
函数式索引
df.loc[lambda x:x['Gender']=='M'].head()#loc中使用的函数,传入参数就是前面的df
        School	Class	Gender	Address	Height	Weight	Math	Physics
ID								
1101	S_1	    C_1	    M	street_1	173	    63	    34.0	A+
1103	S_1	    C_1	    M	street_2	186	    82	    87.2	B+
1201	S_1	    C_2	    M	street_5	188	    68	    97.0	A-
1203	S_1	    C_2	    M	street_6	160	    53	    58.8	A+
1301	S_1	    C_3	    M	street_4	161	    68	    31.5	B+
def f(x):
    return [1101,1103]
df.loc[f]
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1101	S_1	    C_1	    M	    street_1	173	    63	    34.0	A+
1103	S_1	    C_1	    M	    street_2	186	    82	    87.2	B+
布尔索引
df.loc[df['Address'].isin(['street_7','street_4'])].head()
        School	Class	Gender	Address	Height	Weight	Math	Physics
ID								
1105	S_1	    C_1	    F	    street_4	159	    64	84.8	B+
1202	S_1	    C_2	    F	    street_4	176	    94	63.5	B-
1301	S_1	    C_3	    M	    street_4	161	    68	31.5	B+
1303	S_1	    C_3	    M	    street_7	188	    82	49.7	B
2101	S_2	    C_1	    M	    street_7	174	    84	83.3	C
df.loc[[True if i[-1]=='4' or i[-1]=='7' else False for i in df['Address'].values]].head()
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1105	S_1	    C_1	    F	    street_4	159	    64	84.8	B+
1202	S_1	    C_2	    F	    street_4	176	    94	63.5	B-
1301	S_1	    C_3	    M	    street_4	161	    68	31.5	B+
1303	S_1 	C_3	    M	    street_7	188	    82	49.7	B
2101	S_2	    C_1	    M	    street_7	174	    84	83.3	C

本质上说,loc中能传入的只有布尔列表和索引子集构成的列表,只要把握这个原则就很容易理解上面那些操作

iloc方法

注意与loc不同,切片右端点不包含

单行索引
df.iloc[3]
School          S_1
Class           C_1
Gender            F
Address    street_2
Height          167
Weight           81
Math           80.4
Physics          B-
Name: 1104, dtype: object
多行索引
df.iloc[3:5]
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1104	S_1 	C_1	    F	    street_2	167	    81  	80.4	B-
1105	S_1	    C_1	    F	    street_4	159	    64	    84.8	B+
单列索引
df.iloc[:,3].head()
ID
1101    street_1
1102    street_2
1103    street_2
1104    street_2
1105    street_4
Name: Address, dtype: object
多列索引
df.iloc[:,7::-2].head()
    Physics	Weight	Address	    Class
ID				
1101	A+	63  	street_1	C_1
1102	B+	73	    street_2	C_1
1103	B+	82	    street_2	C_1
1104	B-	81	    street_2	C_1
1105	B+	64	    street_4	C_1
混合索引
df.iloc[3::4,7::-2].head()
    Physics	Weight	Address 	Class
ID				
1104	B-	81	    street_2	C_1
1203	A+	53	    street_6	C_2
1302	A-	57	    street_1	C_3
2101	C	84	    street_7	C_1
2105	A	81	    street_4	C_1
函数式索引
df.iloc[lambda x:[3]].head()
    School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1104	S_1	C_1	    F	    street_2	167	    81	    80.4	B-

小节:由上所述,iloc中接收的参数只能为整数或整数列表,不能使用布尔索引

[]操作符

如果不想陷入困境,请不要在行索引为浮点时使用[]操作符,因为在Series中的浮点[]并不是进行位置比较,而是值比较,非常特殊

Series[]操作
单元素索引
s = pd.Series(df['Math'],index=df.index)
s[1101]#使用的是索引标签
34.0
多行索引
s[0:4]#使用的是绝对位置的整数切片,与元素无关,这里容易混淆
ID
1101    34.0
1102    32.5
1103    87.2
1104    80.4
Name: Math, dtype: float64
函数式索引
s[lambda x: x.index[16::-6]]#注意使用lambda函数时,直接切片(如:s[lambda x: 16::-6])就报错,此时使用的不是绝对位置切片,而是元素切片,非常易错
ID
2102    50.6
1301    31.5
1105    84.8
Name: Math, dtype: float64
布尔索引
s[s>80]
ID
1103    87.2
1104    80.4
1105    84.8
1201    97.0
1302    87.7
1304    85.2
2101    83.3
2205    85.4
2304    95.5
Name: Math, dtype: float64
DataFrame[]操作
单行索引
df[1:2]
#这里非常容易写成df['label'],会报错
#同Series使用了绝对位置切片
#如果想要获得某一个元素,可用如下get_loc方法:
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1102	S_1	    C_1	    F	    street_2	192	    73	    32.5	B+
row = df.index.get_loc(1102)
df[row:row+1]
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1102	S_1	    C_1	    F	    street_2	192	    73	    32.5	B+
多行索引
#用切片,如果是选取指定的某几行,推荐使用loc,否则很可能报错
df[3:5]
    School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1104	S_1	C_1	    F	    street_2	167	    81	    80.4	B-
1105	S_1	C_1	    F	    street_4	159	    64	    84.8	B+
单列索引
df['School'].head()
ID
1101    S_1
1102    S_1
1103    S_1
1104    S_1
1105    S_1
Name: School, dtype: object
多列索引
df[['School','Math']].head()
    School	Math
ID		
1101	S_1	34.0
1102	S_1	32.5
1103	S_1	87.2
1104	S_1	80.4
1105	S_1	84.8
函数式索引
df[lambda x:['Math','Physics']].head()
        Math	Physics
ID		
1101	34.0	A+
1102	32.5	B+
1103	87.2	B+
1104	80.4	B-
1105	84.8	B+
布尔索引
df[df['Gender']=='F'].head()

    School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1102	S_1	C_1	    F	    street_2	192	    73	    32.5	B+
1104	S_1	C_1	    F	    street_2	167	    81	    80.4	B-
1105	S_1	C_1	    F	    street_4	159	    64	    84.8	B+
1202	S_1	C_2	    F	    street_4	176	    94	    63.5	B-
1204	S_1	C_2	    F	    street_5	162	    63	    33.8	B

一般来说,[]操作符常用于列选择或布尔选择,尽量避免行的选择

布尔索引

布尔符号

&,|,~:分别代表和and,或or,取反not

df[(df['Gender']=='F')&(df['Address']=='street_2')].head()
    School	    Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1102	S_1	    C_1 	F	    street_2	192	    73	    32.5	B+
1104	S_1	    C_1	    F	    street_2	167	    81	    80.4	B-
2401	S_2	    C_4	    F	    street_2	192	    62	    45.3	A
2404	S_2	    C_4	    F	    street_2	160	    84	    67.7	B
df[(df['Math']>85)|(df['Address']=='street_7')].head()
        School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1103	S_1	    C_1	    M	    street_2	186	    82	    87.2	B+
1201	S_1	    C_2	    M	    street_5	188	    68	    97.0	A-
1302	S_1	    C_3	    F	    street_1	175	    57	    87.7	A-
1303	S_1	    C_3	    M	    street_7	188	    82	    49.7	B
1304	S_1	    C_3	    M	    street_2	195	    70  	85.2	A
df[~((df['Math']>75)|(df['Address']=='street_1'))].head()
    School	Class	Gender	Address	    Height	Weight	Math	Physics
ID								
1102	S_1	C_1 	F	    street_2	192	    73	    32.5	B+
1202	S_1	C_2	    F	    street_4	176	    94	    63.5	B-
1203	S_1	C_2	    M	    street_6	160	    53	    58.8	A+
1204	S_1	C_2	    F	    street_5	162	    63  	33.8	B
1205	S_1	C_2	    F	    street_6	167	    63	    68.4	B-

loc[]中相应位置都能使用布尔列表选择:

df.loc[df['Math']>60,(df[:8]['Address']=='street_6').values].head()
#如果不加values就会索引对齐发生错误,Pandas中的索引对齐是一个重要特征,很多时候非常实用
#但是若不加以留意,就会埋下隐患
Physics
ID	
1103	B+
1104	B-
1105	B+
1201	A-
1202	B-

isin方法

df[df['Address'].isin(['street_1','street_4'])&df['Physics'].isin(['A','A+'])]
    School	Class	Gender	Address	Height	Weight	Math	Physics
ID								
1101	S_1	C_1	    M	    street_1	173 	63	34.0	A+
2105	S_2	C_1	    M	    street_4	170	    81	34.2	A
2203	S_2	C_2	    M	    street_4	155	    91	73.8	A+
#上面也可以用字典方式写:
df[df[['Address','Physics']].isin({'Address':['street_1','street_4'],'Physics':['A','A+']}).all(1)]
#all与&的思路是类似的,其中的1代表按照跨列方向判断是否全为True
    School	Class	Gender	Address	Height	Weight	Math	Physics
ID								
1101	S_1	C_1	    M	street_1	173	    63	    34.0	A+
2105	S_2	C_1	    M	street_4	170	    81  	34.2	A
2203	S_2	C_2	    M	street_4	155	    91	    73.8	A+

快速标量索引

当只需要取一个元素时,atiat方法能够提供更快的实现:

display(df.at[1101,'School'])
display(df.loc[1101,'School'])
display(df.iat[0,0])
display(df.iloc[0,0])
#可尝试去掉注释对比时间
#%timeit df.at[1101,'School']
#%timeit df.loc[1101,'School']
#%timeit df.iat[0,0]
#%timeit df.iloc[0,0]
'S_1'
'S_1'
'S_1'
'S_1'

区间索引

此处介绍并不是说只能在单级索引中使用区间索引,只是作为一种特殊类型的索引方式,在此处先行介绍

利用interval_range方法

pd.interval_range(start=0,end=5)
#closed参数可选'left''right''both''neither',默认左开右闭
IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
              closed='right',
              dtype='interval[int64]')
pd.interval_range(start=0,periods=8,freq=5)
#periods参数控制区间个数,freq控制步长
IntervalIndex([(0, 5], (5, 10], (10, 15], (15, 20], (20, 25], (25, 30], (30, 35], (35, 40]],
              closed='right',
              dtype='interval[int64]')

利用cut将数值列转为区间为元素的分类变量,例如统计数学成绩的区间情况:

math_interval = pd.cut(df['Math'],bins=[0,40,60,80,100])
#注意,如果没有类型转换,此时并不是区间类型,而是category类型
math_interval.head()
ID
1101      (0, 40]
1102      (0, 40]
1103    (80, 100]
1104    (80, 100]
1105    (80, 100]
Name: Math, dtype: category
Categories (4, interval[int64]): [(0, 40] < (40, 60] < (60, 80] < (80, 100]]

区间索引的选取

df_i = df.join(math_interval,rsuffix='_interval')[['Math','Math_interval']]\
            .reset_index().set_index('Math_interval')
df_i.head()
ID	Math
Math_interval		
(0, 40]	1101	34.0
(0, 40]	1102	32.5
(80, 100]	1103	87.2
(80, 100]	1104	80.4
(80, 100]	1105	84.8
df_i.loc[65].head()
#包含该值就会被选中
            ID	    Math
Math_interval		
(60, 80]	1202	63.5
(60, 80]	1205	68.4
(60, 80]	1305	61.7
(60, 80]	2104	72.2
(60, 80]	2202	68.5
df_i.loc[[65,90]].head()
            ID	    Math
Math_interval		
(60, 80]	1202	63.5
(60, 80]	1205	68.4
(60, 80]	1305	61.7
(60, 80]	2104	72.2
(60, 80]	2202	68.5

如果想要选取某个区间,先要把分类变量转为区间变量,再使用overlap方法:

#df_i.loc[pd.Interval(70,75)].head() 报错
df_i[df_i.index.astype('interval').overlaps(pd.Interval(70, 85))].head()
            ID	    Math
Math_interval		
(80, 100]	1103	87.2
(80, 100]	1104	80.4
(80, 100]	1105	84.8
(80, 100]	1201	97.0
(60, 80]	1202	63.5

多级索引

创建多级索引

通过from_tuplefrom_arrays

直接创建元组
tuples = [('A','a'),('A','b'),('B','a'),('B','b')]
mul_index = pd.MultiIndex.from_tuples(tuples, names=('Upper', 'Lower'))
mul_index
MultiIndex([('A', 'a'),
            ('A', 'b'),
            ('B', 'a'),
            ('B', 'b')],
           names=['Upper', 'Lower'])
pd.DataFrame({'Score':['perfect','good','fair','bad']},index=mul_index)
            Score
Upper	Lower	
A	    a	perfect
        b	good
B	    a	fair
        b	bad
利用zip创建元组
L1 = list('AABB')
L2 = list('abab')
tuples = list(zip(L1,L2))
mul_index = pd.MultiIndex.from_tuples(tuples, names=('Upper', 'Lower'))
pd.DataFrame({'Score':['perfect','good','fair','bad']},index=mul_index)
                Score
Upper	Lower	
A	    a	    perfect
        b	    good
B	    a	    fair
        b	    bad
通过Array创建
arrays = [['A','a'],['A','b'],['B','a'],['B','b']]
mul_index = pd.MultiIndex.from_tuples(arrays, names=('Upper', 'Lower'))
pd.DataFrame({'Score':['perfect','good','fair','bad']},index=mul_index)
            Score
Upper	Lower	
A	    a	perfect
        b	good
B	    a	fair
        b	bad
mul_index
#由此看出内部自动转成元组
MultiIndex([('A', 'a'),
            ('A', 'b'),
            ('B', 'a'),
            ('B', 'b')],
           names=['Upper', 'Lower'])
通过from_product
L1 = ['A','B']
L2 = ['a','b']
pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
#两两相乘
MultiIndex([('A', 'a'),
            ('A', 'b'),
            ('B', 'a'),
            ('B', 'b')],
           names=['Upper', 'Lower'])
指定df中的列创建(set_index方法)
df_using_mul = df.set_index(['Class','Address'])
df_using_mul.head()

多层索引切片

df_using_mul.head()

一般切片

#df_using_mul.loc['C_2','street_5']
#当索引不排序时,单个索引会报出性能警告
#df_using_mul.index.is_lexsorted()
#该函数检查是否排序
df_using_mul.sort_index().loc['C_2','street_5']
#df_using_mul.sort_index().index.is_lexsorted()
#df_using_mul.loc[('C_2','street_5'):] 报错
#当不排序时,不能使用多层切片
df_using_mul.sort_index().loc[('C_2','street_6'):('C_3','street_4')]
#注意此处由于使用了loc,因此仍然包含右端点
df_using_mul.sort_index().loc[('C_2','street_7'):'C_3'].head()
#非元组也是合法的,表示选中该层所有元素

第一类特殊情况:由元组构成列表

df_using_mul.sort_index().loc[[('C_2','street_7'),('C_3','street_2')]]
#表示选出某几个元素,精确到最内层索引

第二类特殊情况:由列表构成元组

df_using_mul.sort_index().loc[(['C_2','C_3'],['street_4','street_7']),:]
#选出第一层在‘C_2’和'C_3'中且第二层在'street_4'和'street_7'中的行

多层索引中的slice对象

L1,L2 = ['A','B','C'],['a','b','c']
mul_index1 = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
L3,L4 = ['D','E','F'],['d','e','f']
mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))
df_s = pd.DataFrame(np.random.rand(9,9),index=mul_index1,columns=mul_index2)
df_s


索引Slice的使用非常灵活:

df_s.loc[idx['B':,df_s['D']['d']>0.3],idx[df_s.sum()>4]]
#df_s.sum()默认为对列求和,因此返回一个长度为9的数值列表

索引层的交换

swaplevel方法(两层交换)

df_using_mul.head()
df_using_mul.swaplevel(i=1,j=0,axis=0).sort_index().head()

reorder_levels方法(多层交换)

df_muls = df.set_index(['School','Class','Address'])
df_muls.head()
df_muls.reorder_levels([2,0,1],axis=0).sort_index().head()
#如果索引有name,可以直接使用name
df_muls.reorder_levels(['Address','School','Class'],axis=0).sort_index().head()

索引设定

index_col参数

index_colread_csv中的一个参数,而不是某一个方法:

pd.read_csv('data/table.csv',index_col=['Address','School']).head()

reindexreindex_like

reindex是指重新索引,它的重要特性在于索引对齐,很多时候用于重新排序

df.head()
df.reindex(index=[1101,1203,1206,2402])

df.reindex(columns=['Height','Gender','Average']).head()


可以选择缺失值的填充方法:fill_valuemethodbfill/ffill/nearest),其中method参数必须索引单调

df.reindex(index=[1101,1203,1206,2402],method='bfill')
#bfill表示用所在索引1206的后一个有效行填充,ffill为前一个有效行,nearest是指最近的
df.reindex(index=[1101,1203,1206,2402],method='nearest')
#数值上1205比1301更接近1206,因此用前者填充


reindex_like的作用为生成一个横纵索引完全与参数列表一致的DataFrame,数据使用被调用的表

df_temp = pd.DataFrame({'Weight':np.zeros(5),
                        'Height':np.zeros(5),
                        'ID':[1101,1104,1103,1106,1102]}).set_index('ID')
df_temp.reindex_like(df[0:5][['Weight','Height']])

set_indexreset_index

先介绍set_index:从字面意思看,就是将某些列作为索引
使用表内列作为索引:

df.head()
df.set_index('Class').head()


利用append参数可以将当前索引维持不变

df.set_index('Class',append=True).head()


当使用与表长相同的列作为索引(需要先转化为Series,否则报错):

df.set_index(pd.Series(range(df.shape[0]))).head()


可以直接添加多级索引

df.set_index([pd.Series(range(df.shape[0])),pd.Series(np.ones(df.shape[0]))]).head()


下面介绍reset_index方法,它的主要功能是将索引重置
默认状态直接恢复到自然数索引:

df.reset_index().head()


level参数指定哪一层被reset,用col_level参数指定set到哪一层

L1,L2 = ['A','B','C'],['a','b','c']
mul_index1 = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
L3,L4 = ['D','E','F'],['d','e','f']
mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))
df_temp = pd.DataFrame(np.random.rand(9,9),index=mul_index1,columns=mul_index2)
df_temp.head()
df_temp1 = df_temp.reset_index(level=1,col_level=1)
df_temp1.head()
df_temp1.columns
#看到的确插入了level2
MultiIndex([( '', 'Lower'),
            ('D',     'd'),
            ('D',     'e'),
            ('D',     'f'),
            ('E',     'd'),
            ('E',     'e'),
            ('E',     'f'),
            ('F',     'd'),
            ('F',     'e'),
            ('F',     'f')],
           names=['Big', 'Small'])
df_temp1.index
#最内层索引被移出
Index(['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C'], dtype='object', name='Upper')

rename_axisrename

rename_axis是针对多级索引的方法,作用是修改某一层的索引名,而不是索引标签

df_temp.rename_axis(index={'Lower':'LowerLower'},columns={'Big':'BigBig'})


rename方法用于修改列或者行索引标签,而不是索引名:

df_temp.rename(index={'A':'T'},columns={'e':'changed_e'}).head()

常用索引型函数

where函数

当对条件为False的单元进行填充:

df.head()
df.where(df['Gender']=='M').head()
#不满足条件的行全部被设置为NaN


通过这种方法筛选结果和[]操作符的结果完全一致:

df.where(df['Gender']=='M').dropna().head()


第一个参数为布尔条件,第二个参数为填充值:

df.where(df['Gender']=='M',np.random.rand(df.shape[0],df.shape[1])).head()

mask函数

mask函数与where功能上相反,其余完全一致,即对条件为True的单元进行填充

df.mask(df['Gender']=='M').dropna().head()
df.mask(df['Gender']=='M',np.random.rand(df.shape[0],df.shape[1])).head()

query函数

df.head()


query函数中的布尔表达式中,下面的符号都是合法的:行列索引名、字符串、and/not/or/&/|/~/not in/in/==/!=、四则运算符

df.query('(Address in ["street_6","street_7"])&(Weight>(70+10))&(ID in [1303,2304,2402])')

重复元素处理

duplicated方法

该方法返回了是否重复的布尔列表

df.duplicated('Class').head()
ID
1101    False
1102     True
1103     True
1104     True
1105     True
dtype: bool

可选参数keep默认为first,即首次出现设为不重复,若为last,则最后一次设为不重复,若为False,则所有重复项为False

df.duplicated('Class',keep='last').tail()
ID
2401     True
2402     True
2403     True
2404     True
2405    False
dtype: bool
df.duplicated('Class',keep=False).head()
ID
1101    True
1102    True
1103    True
1104    True
1105    True
dtype: bool

drop_duplicates方法

从名字上看出为剔除重复项,这在后面章节中的分组操作中可能是有用的,例如需要保留每组的第一个值

df.drop_duplicates('Class')


参数与duplicate函数类似:

df.drop_duplicates('Class',keep='last')

在传入多列时等价于将多列共同视作一个多级索引,比较重复项:

df.drop_duplicates(['School','Class'])

抽样函数

这里的抽样函数指的就是sample函数

n为样本量

df.sample(n=5)

frac为抽样比

df.sample(frac=0.05)

replace为是否放回

df.sample(n=df.shape[0],replace=True).head()
df.sample(n=35,replace=True).index.is_unique
False

axis为抽样维度,默认为0,即抽行

df.sample(n=3,axis=1).head()

weights为样本权重,自动归一化

df.sample(n=3,weights=np.random.rand(df.shape[0])).head()
#以某一列为权重,这在抽样理论中很常见
df.sample(n=3,weights=df['Math']).head()

练习

练习一

现有一份关于UFO的数据集,请解决下列问题:

pd.read_csv('data/UFO.csv').head()
  • 在所有被观测时间超过60s的时间中,哪个形状最多?
df = pd.read_csv('data/UFO.csv')
df.rename(columns={'duration (seconds)':'duration'},inplace=True)
df['duration'].astype('float')
df.head()
df.query('duration > 60')['shape'].value_counts().index[0]
'light'
  • 对经纬度进行划分:-180°至180°以30°为一个划分,-90°至90°以18°为一个划分,请问哪个区域中报告的UFO事件数量最多?
bins_long = np.linspace(-180,180,13).tolist()
bins_la = np.linspace(-90,90,11).tolist()
cuts_long = pd.cut(df['longitude'],bins=bins_long)
df['cuts_long'] = cuts_long
cuts_la = pd.cut(df['latitude'],bins=bins_la)
df['cuts_la'] = cuts_la
df.head()
df.set_index(['cuts_long','cuts_la']).index.value_counts().head()
((-90.0, -60.0], (36.0, 54.0])      27891
((-120.0, -90.0], (18.0, 36.0])     14280
((-120.0, -90.0], (36.0, 54.0])     11960
((-90.0, -60.0], (18.0, 36.0])       9923
((-150.0, -120.0], (36.0, 54.0])     9658
dtype: int64

练习二

现有一份关于口袋妖怪的数据集,请解决下列问题:

pd.read_csv('data/Pokemon.csv').head()
  • 双属性的Pokemon占总体比例的多少?
df = pd.read_csv('data/Pokemon.csv')
df.head()
df['Type 2'].count()/df.shape[0]
0.5175
  • 在所有种族值(Total)不小于580的Pokemon中,非神兽(Legendary=False)的比例为多少?
df.query('Total >= 580')['Legendary'].value_counts(normalize=True)
True     0.575221
False    0.424779
Name: Legendary, dtype: float64
  • 在第一属性为格斗系(Fighting)的Pokemon中,物攻排名前三高的是哪些?
df[df['Type 1']=='Fighting'].sort_values(by='Attack',ascending=False).iloc[:3]
  • 请问六项种族指标(HP、物攻、特攻、物防、特防、速度)极差的均值最大的是哪个属性(只考虑第一属性,且均值是对属性而言)?
df['range'] = df.iloc[:,5:11].max(axis=1)-df.iloc[:,5:11].min(axis=1)
attribute = df[['Type 1','range']].set_index('Type 1')
max_range = 0
result = ''
for i in attribute.index.unique():
    temp = attribute.loc[i,:].mean()
    if temp.values[0] > max_range:
        max_range = temp.values[0]
        result = i
result
'Steel'
  • 哪个属性(只考虑第一属性)的神兽比例最高?该属性神兽的种族值也是最高的吗?
df.query('Legendary == True')['Type 1'].value_counts(normalize=True).index[0]
'Psychic'
attribute = df.query('Legendary == True')[['Type 1','Total']].set_index('Type 1')
max_value = 0
result = ''
for i in attribute.index.unique()[:-1]:
    temp = attribute.loc[i,:].mean()
    if temp[0] > max_value:
        max_value = temp[0]
        result = i
result
'Normal'

分组

import numpy as np
import pandas as pd
df = pd.read_csv('data/table.csv',index_col='ID')
df.head()

SAC过程

内涵

SAC指的是分组操作中的split-apply-combine过程,其中split指基于某一些规则,将数据拆成若干组,apply是指对每一组独立地使用函数,combine指将每一组的结果组合成某一类数据结构

apply过程

在该过程中,我们实际往往会遇到三类问题:

  • 整合(Aggregation)——即分组计算统计量(如求均值、求每组元素个数)
  • 变换(Transformation)——即分组对每个单元的数据进行操作(如元素标准化)
  • 过滤(Filtration)——即按照某些规则筛选出一些组(如选出组内某一指标小于50的组)

groupby函数

分组函数的基本内容

根据某一列分组

grouped_single = df.groupby('School')

经过groupby后会生成一个groupby对象,该对象本身不会返回任何东西,只有当相应的方法被调用才会起作用
例如取出某一个组:

grouped_single.get_group('S_1').head()

根据某几列分组

grouped_mul = df.groupby(['School','Class'])
grouped_mul.get_group(('S_2','C_4'))

组容量与组数

grouped_single.size()
School
S_1    15
S_2    20
dtype: int64
grouped_mul.size()
School  Class
S_1     C_1      5
        C_2      5
        C_3      5
S_2     C_1      5
        C_2      5
        C_3      5
        C_4      5
dtype: int64
grouped_single.ngroups
2
grouped_mul.ngroups
7

组的遍历

for name,group in grouped_single:
    print(name)
    display(group.head())

level参数(用于多级索引)和axis参数

df.set_index(['Gender','School']).groupby(level=1,axis=0).get_group('S_1').head()

groupby对象的特点

查看所有可调用的方法

由此可见,groupby对象可以使用相当多的函数,灵活程度很高

print([attr for attr in dir(grouped_single) if not attr.startswith('_')])

['Address', 'Class', 'Gender', 'Height', 'Math', 'Physics', 'School', 'Weight', 'agg', 'aggregate', 'all', 'any', 'apply', 'backfill', 'bfill', 'boxplot', 'corr', 'corrwith', 'count', 'cov', 'cumcount', 'cummax', 'cummin', 'cumprod', 'cumsum', 'describe', 'diff', 'dtypes', 'expanding', 'ffill', 'fillna', 'filter', 'first', 'get_group', 'groups', 'head', 'hist', 'idxmax', 'idxmin', 'indices', 'last', 'mad', 'max', 'mean', 'median', 'min', 'ndim', 'ngroup', 'ngroups', 'nth', 'nunique', 'ohlc', 'pad', 'pct_change', 'pipe', 'plot', 'prod', 'quantile', 'rank', 'resample', 'rolling', 'sem', 'shift', 'size', 'skew', 'std', 'sum', 'tail', 'take', 'transform', 'tshift', 'var']

分组对象的headfirst

对分组对象使用head函数,返回的是每个组的前几行,而不是数据集前几行

grouped_single.head(2)


first显示的是以分组为索引的每组的第一个分组信息

grouped_single.first()

分组依据

对于groupby函数而言,分组的依据是非常自由的,只要是与数据框长度相同的列表即可,同时支持函数型分组

df.groupby(np.random.choice(['a','b','c'],df.shape[0])).get_group('a').head()
#相当于将np.random.choice(['a','b','c'],df.shape[0])当做新的一列进行分组


根据奇偶行分组

df.groupby(lambda x:'奇数行' if not df.index.get_loc(x)%2==1 else '偶数行').groups
{'偶数行': Int64Index([1102, 1104, 1201, 1203, 1205, 1302, 1304, 2101, 2103, 2105, 2202,
             2204, 2301, 2303, 2305, 2402, 2404],
            dtype='int64', name='ID'),
 '奇数行': Int64Index([1101, 1103, 1105, 1202, 1204, 1301, 1303, 1305, 2102, 2104, 2201,
             2203, 2205, 2302, 2304, 2401, 2403, 2405],
            dtype='int64', name='ID')}

如果是多层索引,那么lambda表达式中的输入就是元组,下面实现的功能为查看两所学校中男女生分别均分是否及格
注意:此处只是演示groupby的用法,实际操作不会这样写

math_score = df.set_index(['Gender','School'])['Math'].sort_index()
grouped_score = df.set_index(['Gender','School']).sort_index().\
            groupby(lambda x:(x,'均分及格' if math_score[x].mean()>=60 else '均分不及格'))
for name,_ in grouped_score:print(name)

(('F', 'S_1'), '均分及格')
(('F', 'S_2'), '均分及格')
(('M', 'S_1'), '均分及格')
(('M', 'S_2'), '均分不及格')

groupby[]操作

可以用[]选出groupby对象的某个或者某几个列,上面的均分比较可以如下简洁地写出:

df.groupby(['Gender','School'])['Math'].mean()>=60
Gender  School
F       S_1        True
        S_2        True
M       S_1        True
        S_2       False
Name: Math, dtype: bool

用列表可选出多个属性列:

df.groupby(['Gender','School'])[['Math','Height']].mean()

连续型变量分组

例如利用cut函数对数学成绩分组:

bins = [0,40,60,80,90,100]
cuts = pd.cut(df['Math'],bins=bins) #可选label添加自定义标签
df.groupby(cuts)['Math'].count()
Math
(0, 40]       7
(40, 60]     10
(60, 80]      9
(80, 90]      7
(90, 100]     2
Name: Math, dtype: int64

聚合、过滤和变换

聚合(Aggregation

常用聚合函数

所谓聚合就是把一堆数,变成一个标量,因此mean/sum/size/count/std/var/sem/describe/first/last/nth/min/max都是聚合函数

同时使用多个聚合函数

group_m = grouped_single['Math']


利用元组进行重命名

group_m.agg([('rename_sum','sum'),('rename_mean','mean')])


指定哪些函数作用哪些列

grouped_mul.agg({'Math':['mean','max'],'Height':'var'})

使用自定义函数

grouped_single['Math'].agg(lambda x:print(x.head(),'间隔'))
#可以发现,agg函数的传入是分组逐列进行的,有了这个特性就可以做许多事情
Series([], Name: Math, dtype: float64) 间隔
1101    34.0
1102    32.5
1103    87.2
1104    80.4
1105    84.8
Name: Math, dtype: float64 间隔
2101    83.3
2102    50.6
2103    52.5
2104    72.2
2105    34.2
Name: Math, dtype: float64 间隔

官方没有提供极差计算的函数,但通过agg可以容易地实现组内极差计算

grouped_single['Math'].agg(lambda x:x.max()-x.min())
School
S_1    65.5
S_2    62.8
Name: Math, dtype: float64

利用NamedAgg函数进行多个聚合

不支持lambda函数,但是可以使用外置的def函数

def R1(x):
    return x.max()-x.min()
def R2(x):
    return x.max()-x.median()
grouped_single['Math'].agg(min_score1=pd.NamedAgg(column='col1', aggfunc=R1),
                           max_score1=pd.NamedAgg(column='col2', aggfunc='max'),
                           range_score2=pd.NamedAgg(column='col3', aggfunc=R2)).head()

带参数的聚合函数

判断是否组内数学分数至少有一个值在50-52之间:

def f(s,low,high):
    return s.between(low,high).max()
grouped_single['Math'].agg(f,50,52)
School
S_1    False
S_2     True
Name: Math, dtype: bool

如果需要使用多个函数,并且其中至少有一个带参数,则使用wrap技巧:

def f_test(s,low,high):
    return s.between(low,high).max()
def agg_f(f_mul,name,*args,**kwargs):
    def wrapper(x):
        return f_mul(x,*args,**kwargs)
    wrapper.__name__ = name
    return wrapper
new_f = agg_f(f_test,'at_least_one_in_50_52',50,52)
grouped_single['Math'].agg([new_f,'mean']).head()

过滤(Filteration

filter函数是用来筛选某些组的(务必记住结果是组的全体),因此传入的值应当是布尔标量

grouped_single[['Math','Physics']].filter(lambda x:(x['Math']>32).all()).head()

变换(Transformation

传入对象

transform函数中传入的对象是组内的列,并且返回值需要与列长完全一致

grouped_single[['Math','Height']].transform(lambda x:x-x.min()).head()


如果返回了标量值,那么组内的所有元素会被广播为这个值

grouped_single[['Math','Height']].transform(lambda x:x.mean()).head()

利用变换方法进行组内标准化

grouped_single[['Math','Height']].transform(lambda x:(x-x.mean())/x.std()).head()

利用变换方法进行组内缺失值的均值填充

df_nan = df[['Math','School']].copy().reset_index()
df_nan.loc[np.random.randint(0,df.shape[0],25),['Math']]=np.nan
df_nan.head()
df_nan.groupby('School').transform(lambda x: x.fillna(x.mean())).join(df.reset_index()['School']).head()

apply函数

apply函数的灵活性

可能在所有的分组函数中,apply是应用最为广泛的,这得益于它的灵活性:
对于传入值而言,从下面的打印内容可以看到是以分组的表传入apply中:

df.groupby('School').apply(lambda x:print(x.head(1)))
     School Class Gender   Address  Height  Weight  Math Physics
ID                                                              
1101    S_1   C_1      M  street_1     173      63  34.0      A+
     School Class Gender   Address  Height  Weight  Math Physics
ID                                                              
2101    S_2   C_1      M  street_7     174      84  83.3       C

apply函数的灵活性很大程度来源于其返回值的多样性:

标量返回值

df[['School','Math','Height']].groupby('School').apply(lambda x:x.max())

列表返回值

df[['School','Math','Height']].groupby('School').apply(lambda x:x-x.min()).head()

数据框返回值

df[['School','Math','Height']].groupby('School')\
    .apply(lambda x:pd.DataFrame({'col1':x['Math']-x['Math'].max(),
                                  'col2':x['Math']-x['Math'].min(),
                                  'col3':x['Height']-x['Height'].max(),
                                  'col4':x['Height']-x['Height'].min()})).head()

apply同时统计多个指标

此处可以借助OrderedDict工具进行快捷的统计:

from collections import OrderedDict
def f(df):
    data = OrderedDict()
    data['M_sum'] = df['Math'].sum()
    data['W_var'] = df['Weight'].var()
    data['H_mean'] = df['Height'].mean()
    return pd.Series(data)
grouped_single.apply(f)

练习

练习一

现有一份关于diamonds的数据集,列分别记录了克拉数、颜色、开采深度、价格,请解决下列问题:

pd.read_csv('data/Diamonds.csv').head()
  • 在所有重量超过1克拉的钻石中,价格的极差是多少?
df_r = df.query('carat>1')['price']
df_r.max()-df_r.min()
17561
  • 若以开采深度的0.2\0.4\0.6\0.8分位数为分组依据,每一组中钻石颜色最多的是哪一种?该种颜色是组内平均而言单位重量最贵的吗?
bins = df['depth'].quantile(np.linspace(0,1,6)).tolist()
cuts = pd.cut(df['depth'],bins=bins) #可选label添加自定义标签
df['cuts'] = cuts
df.head()
color_result = df.groupby('cuts')['color'].describe()
color_result


前三个分位数区间不满足条件,后两个区间中数量最多的颜色的确是均重价格中最贵的

df['均重价格']=df['price']/df['carat']
color_result['top'] == [i[1] for i in df.groupby(['cuts','color'])['均重价格'].mean().groupby(['cuts']).idxmax().values]
cuts
(43.0, 60.8]    False
(60.8, 61.6]    False
(61.6, 62.1]    False
(62.1, 62.7]     True
(62.7, 79.0]     True
Name: top, dtype: bool
  • 以重量分组(0-0.5,0.5-1,1-1.5,1.5-2,2+),按递增的深度为索引排序,求每组中连续的严格递增价格序列长度的最大值。
df = df.drop(columns='均重价格')
cuts = pd.cut(df['carat'],bins=[0,0.5,1,1.5,2,np.inf]) #可选label添加自定义标签
df['cuts'] = cuts
df.head()
def f(nums):
    if not nums:        
        return 0
    res = 1                            
    cur_len = 1                        
    for i in range(1, len(nums)):      
        if nums[i-1] < nums[i]:        
            cur_len += 1                
            res = max(cur_len, res)     
        else:                       
            cur_len = 1                 
    return res
for name,group in df.groupby('cuts'):
    group = group.sort_values(by='depth')
    s = group['price']
    print(name,f(s.tolist()))
(0.0, 0.5] 8
(0.5, 1.0] 8
(1.0, 1.5] 7
(1.5, 2.0] 11
(2.0, inf] 7
  • 请按颜色分组,分别计算价格关于克拉数的回归系数。(单变量的简单线性回归,并只使用PandasNumpy完成)
for name,group in df[['carat','price','color']].groupby('color'):
    L1 = np.array([np.ones(group.shape[0]),group['carat']]).reshape(2,group.shape[0])
    L2 = group['price']
    result = (np.linalg.inv(L1.dot(L1.T)).dot(L1)).dot(L2).reshape(2,1)
    print('当颜色为%s时,截距项为:%f,回归系数为:%f'%(name,result[0],result[1]))
当颜色为D时,截距项为:-2361.017152,回归系数为:8408.353126
当颜色为E时,截距项为:-2381.049600,回归系数为:8296.212783
当颜色为F时,截距项为:-2665.806191,回归系数为:8676.658344
当颜色为G时,截距项为:-2575.527643,回归系数为:8525.345779
当颜色为H时,截距项为:-2460.418046,回归系数为:7619.098320
当颜色为I时,截距项为:-2878.150356,回归系数为:7761.041169
当颜色为J时,截距项为:-2920.603337,回归系数为:7094.192092

练习二

有一份关于美国10年至17年的非法药物数据集,列分别记录了年份、州(5个)、县、药物类型、报告数量,请解决下列问题:

pd.read_csv('data/Drugs.csv').head()
  • 按照年份统计,哪个县的报告数量最多?这个县所属的州在当年也是报告数最多的吗?
idx=pd.IndexSlice
for i in range(2010,2018):
    county = (df.groupby(['COUNTY','YYYY']).sum().loc[idx[:,i],:].idxmax()[0][0])
    state = df.query('COUNTY == "%s"'%county)['State'].iloc[0]
    state_true = df.groupby(['State','YYYY']).sum().loc[idx[:,i],:].idxmax()[0][0]
    if state==state_true:
        print('在%d年,%s县的报告数最多,它所属的州%s也是报告数最多的'%(i,county,state))
    else:
        print('在%d年,%s县的报告数最多,但它所属的州%s不是报告数最多的,%s州报告数最多'%(i,county,state,state_true))
在2010年,PHILADELPHIA县的报告数最多,它所属的州PA也是报告数最多的
在2011年,PHILADELPHIA县的报告数最多,但它所属的州PA不是报告数最多的,OH州报告数最多
在2012年,PHILADELPHIA县的报告数最多,但它所属的州PA不是报告数最多的,OH州报告数最多
在2013年,PHILADELPHIA县的报告数最多,但它所属的州PA不是报告数最多的,OH州报告数最多
在2014年,PHILADELPHIA县的报告数最多,但它所属的州PA不是报告数最多的,OH州报告数最多
在2015年,PHILADELPHIA县的报告数最多,但它所属的州PA不是报告数最多的,OH州报告数最多
在2016年,HAMILTON县的报告数最多,它所属的州OH也是报告数最多的
在2017年,HAMILTON县的报告数最多,它所属的州OH也是报告数最多的
  • 从14年到15年,Heroin的数量增加最多的是哪一个州?它在这个州是所有药物中增幅最大的吗?若不是,请找出符合该条件的药物。
df_b = df[(df['YYYY'].isin([2014,2015]))&(df['SubstanceName']=='Heroin')]
df_add = df_b.groupby(['YYYY','State']).sum()
(df_add.loc[2015]-df_add.loc[2014]).idxmax()
DrugReports    OH
dtype: object
df_b = df[(df['YYYY'].isin([2014,2015]))&(df['State']=='OH')]
df_add = df_b.groupby(['YYYY','SubstanceName']).sum()
display((df_add.loc[2015]-df_add.loc[2014]).idxmax()) #这里利用了索引对齐的特点
display((df_add.loc[2015]/df_add.loc[2014]).idxmax())
DrugReports    Heroin
dtype: object
DrugReports    Acetyl fentanyl
dtype: object

变形

import numpy as np
import pandas as pd
df = pd.read_csv('data/table.csv')
df.head()

透视表

pivot

一般状态下,数据在DataFrame会以压缩(stacked)状态存放,例如上面的Gender,两个类别被叠在一列中,pivot函数可将某一列作为新的cols

df.pivot(index='ID',columns='Gender',values='Height').head()


然而pivot函数具有很强的局限性,除了功能上较少之外,还不允许values中出现重复的行列索引对(pair),例如下面的语句就会报错:

#df.pivot(index='School',columns='Gender',values='Height').head()

因此,更多的时候会选择使用强大的pivot_table函数

pivot_table

pd.pivot_table(df,index='ID',columns='Gender',values='Height').head()


由于功能更多,速度上自然是比不上原来的pivot函数
Pandas中提供了各种选项,下面介绍常用参数:

aggfunc:对组内进行聚合统计,可传入各类函数,默认为'mean'

pd.pivot_table(df,index='School',columns='Gender',values='Height',aggfunc=['mean','sum']).head()

margins:汇总边际状态

pd.pivot_table(df,index='School',columns='Gender',values='Height',aggfunc=['mean','sum'],margins=True).head()
#margins_name可以设置名字,默认为'All'

行、列、值都可以为多级

pd.pivot_table(df,index=['School','Class'],
               columns=['Gender','Address'],
               values=['Height','Weight'])

crosstab(交叉表)

交叉表是一种特殊的透视表,典型的用途如分组统计,如现在想要统计关于街道和性别分组的频数:

pd.crosstab(index=df['Address'],columns=df['Gender'])


交叉表的功能也很强大(但目前还不支持多级分组),下面说明一些重要参数:

valuesaggfunc:分组对某些数据进行聚合操作,这两个参数必须成对出现

pd.crosstab(index=df['Address'],columns=df['Gender'],
            values=np.random.randint(1,20,df.shape[0]),aggfunc='min')
#默认参数等于如下方法:
#pd.crosstab(index=df['Address'],columns=df['Gender'],values=1,aggfunc='count')

除了边际参数margins外,还引入了normalize参数,可选'all','index','columns'参数值

pd.crosstab(index=df['Address'],columns=df['Gender'],normalize='all',margins=True)

其他变形方法

melt

melt函数可以认为是pivot函数的逆操作,将unstacked状态的数据,压缩成stacked,使“宽”的DataFrame变“窄”

df_m = df[['ID','Gender','Math']]
df_m.head()
df.pivot(index='ID',columns='Gender',values='Math').head()


melt函数中的id_vars表示需要保留的列,value_vars表示需要stack的一组列

pivoted = df.pivot(index='ID',columns='Gender',values='Math')
result = pivoted.reset_index().melt(id_vars=['ID'],value_vars=['F','M'],value_name='Math')\
                     .dropna().set_index('ID').sort_index()
#检验是否与展开前的df相同,可以分别将这些链式方法的中间步骤展开,看看是什么结果
result.equals(df_m.set_index('ID'))
True

压缩与展开

stack

这是最基础的变形函数,总共只有两个参数:leveldropna

df_s = pd.pivot_table(df,index=['Class','ID'],columns='Gender',values=['Height','Weight'])
df_s.groupby('Class').head(2)
df_stacked = df_s.stack()
df_stacked.groupby('Class').head(2)


stack函数可以看做将横向的索引放到纵向,因此功能类似与melt,参数level可指定变化的列索引是哪一层(或哪几层,需要列表)

df_stacked = df_s.stack(0)
df_stacked.groupby('Class').head(2)

unstack

stack的逆函数,功能上类似于pivot_table

df_stacked.head()
result = df_stacked.unstack().swaplevel(1,0,axis=1).sort_index(axis=1)
result.equals(df_s)
#同样在unstack中可以指定level参数
True

哑变量与因子化

Dummy Variable(哑变量)

这里主要介绍get_dummies函数,其功能主要是进行one-hot编码:

df_d = df[['Class','Gender','Weight']]
df_d.head()


现在希望将上面的表格前两列转化为哑变量,并加入第三列Weight数值:

pd.get_dummies(df_d[['Class','Gender']]).join(df_d['Weight']).head()
#可选prefix参数添加前缀,prefix_sep添加分隔符

factorize方法

该方法主要用于自然数编码,并且缺失值会被记做-1,其中sort参数表示是否排序后赋值

codes, uniques = pd.factorize(['b', None, 'a', 'c', 'b'], sort=True)
display(codes)
display(uniques)
array([ 1, -1,  0,  2,  1])
array(['a', 'b', 'c'], dtype=object)

练习

练习一

继续使用上一章的药物数据集

pd.read_csv('data/Drugs.csv').head()
  • 现在请你将数据表转化成如下形态,每行需要显示每种药物在每个地区的10年至17年的变化情况,且前三列需要排序:
df = pd.read_csv('data/Drugs.csv',index_col=['State','COUNTY']).sort_index()
df.head()
result = pd.pivot_table(df,index=['State','COUNTY','SubstanceName']
                 ,columns='YYYY'
                 ,values='DrugReports',fill_value='-').reset_index().rename_axis(columns={'YYYY':''})
result.head()
  • 现在请将(a)中的结果恢复到原数据表,并通过equal函数检验初始表与新的结果是否一致(返回True
result_melted = result.melt(id_vars=result.columns[:3],value_vars=result.columns[-8:]
                ,var_name='YYYY',value_name='DrugReports').query('DrugReports != "-"')
result2 = result_melted.sort_values(by=['State','COUNTY','YYYY'
                                    ,'SubstanceName']).reset_index().drop(columns='index')
#下面其实无关紧要,只是交换两个列再改一下类型(因为‘-’所以type变成object了)
cols = list(result2.columns)
a, b = cols.index('SubstanceName'), cols.index('YYYY')
cols[b], cols[a] = cols[a], cols[b]
result2 = result2[cols].astype({'DrugReports':'int','YYYY':'int'})
result2.head()
df_tidy = df.reset_index().sort_values(by=result2.columns[:4].tolist()).reset_index().drop(columns='index')
df_tidy.head()
df_tidy.equals(result2)
True

练习二

现有一份关于某地区地震情况的数据集,请解决如下问题

pd.read_csv('data/Earthquake.csv').head()
  • 现在请你将数据表转化成如下形态,将方向列展开,并将距离、深度和烈度三个属性压缩:
df = pd.read_csv('data/Earthquake.csv')
df = df.sort_values(by=df.columns.tolist()[:3]).sort_index(axis=1).reset_index().drop(columns='index')
df.head()
result = pd.pivot_table(df,index=['日期','时间','维度','经度']
            ,columns='方向'
            ,values=['烈度','深度','距离'],fill_value='-').stack(level=0).rename_axis(index={None:'地震参数'})
result.head(6)
  • 现在请将(a)中的结果恢复到原数据表,并通过equal函数检验初始表与新的结果是否一致(返回True
df_result = result.unstack().stack(0)[(~(result.unstack().stack(0)=='-')).any(1)].reset_index()
df_result.columns.name=None
df_result = df_result.sort_index(axis=1).astype({'深度':'float64','烈度':'float64','距离':'float64'})
df_result.head()
df_result.astype({'深度':'float64','烈度':'float64','距离':'float64'},copy=False).dtypes
Out[42]:
方向     object
日期     object
时间     object
深度    float64
烈度    float64
经度    float64
维度    float64
距离    float64
dtype: object
df.equals(df_result)
True

合并

import numpy as np
import pandas as pd
df = pd.read_csv('data/table.csv')
df.head()

appendassign

append方法

利用序列添加行(必须指定name

df_append = df.loc[:3,['Gender','Height']].copy()
df_append
s = pd.Series({'Gender':'F','Height':188},name='new_row')
df_append.append(s)

DataFrame添加表

df_temp = pd.DataFrame({'Gender':['F','M'],'Height':[188,176]},index=['new_1','new_2'])
df_append.append(df_temp)

assign方法

该方法主要用于添加列,列名直接由参数指定:

s = pd.Series(list('abcd'),index=range(4))
df_append.assign(Letter=s)


可以一次添加多个列:

df_append.assign(col1=lambda x:x['Gender']*2,
                 col2=s)

combineupdate

comine方法

comineupdate都是用于表的填充函数,可以根据某种规则填充

填充对象

可以看出combine方法是按照表的顺序轮流进行逐列循环的,而且自动索引对齐,缺失值为NaN,理解这一点很重要

df_combine_1 = df.loc[:1,['Gender','Height']].copy()
df_combine_2 = df.loc[10:11,['Gender','Height']].copy()
df_combine_1.combine(df_combine_2,lambda x,y:print(x,y))
0       M
1       F
10    NaN
11    NaN
Name: Gender, dtype: object 
0     NaN
1     NaN
10      M
11      F
Name: Gender, dtype: object
0     173.0
1     192.0
10      NaN
11      NaN
Name: Height, dtype: float64
0       NaN
1       NaN
10    161.0
11    175.0
Name: Height, dtype: float64

一些例子

根据列均值的大小填充
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [8, 7], 'B': [6, 5]})
df1.combine(df2,lambda x,y:x if x.mean()>y.mean() else y)
索引对齐特性

默认状态下,后面的表没有的行列都会设置为NaN

df2 = pd.DataFrame({'B': [8, 7], 'C': [6, 5]},index=[1,2])
df1.combine(df2,lambda x,y:x if x.mean()>y.mean() else y)
使得df1原来符合条件的值不会被覆盖
df1.combine(df2,lambda x,y:x if x.mean()>y.mean() else y,overwrite=False)
在新增匹配df2的元素位置填充-1
df1.combine(df2,lambda x,y:x if x.mean()>y.mean() else y,fill_value=-1)

combine_first方法

这个方法作用是用df2填补df1的缺失值,功能比较简单,但很多时候会比combine更常用,下面举两个例子:

df1 = pd.DataFrame({'A': [None, 0], 'B': [None, 4]})
df2 = pd.DataFrame({'A': [1, 1], 'B': [3, 3]})
df1.combine_first(df2)
df1 = pd.DataFrame({'A': [None, 0], 'B': [4, None]})
df2 = pd.DataFrame({'B': [3, 3], 'C': [1, 1]}, index=[1, 2])
df1.combine_first(df2)

update方法

三个特点

  1. 返回的框索引只会与被调用框的一致(默认使用左连接)
  2. 第二个框中的nan元素不会起作用
  3. 没有返回值,直接在df上操作

例子

索引完全对齐情况下的操作
df1 = pd.DataFrame({'A': [1, 2, 3],
                    'B': [400, 500, 600]})
df2 = pd.DataFrame({'B': [4, 5, 6],
                    'C': [7, 8, 9]})
df1.update(df2)
df1
部分填充
df1 = pd.DataFrame({'A': ['a', 'b', 'c'],
                    'B': ['x', 'y', 'z']})
df2 = pd.DataFrame({'B': ['d', 'e']}, index=[1,2])
df1.update(df2)
df1
缺失值不会填充
df1 = pd.DataFrame({'A': [1, 2, 3],
                    'B': [400, 500, 600]})
df2 = pd.DataFrame({'B': [4, np.nan, 6]})
df1.update(df2)
df1

concat方法

concat方法可以在两个维度上拼接,默认纵向凭借(axis=0),拼接方式默认外连接
所谓外连接,就是取拼接方向的并集,而'inner'时取拼接方向(若使用默认的纵向拼接,则为列的交集)的交集
下面举一些例子说明其参数:

df1 = pd.DataFrame({'A': ['A0', 'A1'],
                    'B': ['B0', 'B1']},
                    index = [0,1])
df2 = pd.DataFrame({'A': ['A2', 'A3'],
                    'B': ['B2', 'B3']},
                    index = [2,3])
df3 = pd.DataFrame({'A': ['A1', 'A3'],
                    'D': ['D1', 'D3'],
                    'E': ['E1', 'E3']},
                    index = [1,3])

默认状态拼接:

pd.concat([df1,df2])


axis=1时沿列方向拼接:

pd.concat([df1,df2],axis=1)


join设置为内连接(由于axis=0,因此列取交集):

pd.concat([df3,df1],join='inner')


join设置为外链接:

pd.concat([df3,df1],join='outer',sort=True) #sort设置列排序,默认为False


verify_integrity检查列是否唯一:

#pd.concat([df3,df1],verify_integrity=True,sort=True) 报错

同样,可以添加Series

s = pd.Series(['X0', 'X1'], name='X')
pd.concat([df1,s],axis=1)


key参数用于对不同的数据框增加一个标号,便于索引:

pd.concat([df1,df2], keys=['x', 'y'])
pd.concat([df1,df2], keys=['x', 'y']).index
MultiIndex([('x', 0),
            ('x', 1),
            ('y', 2),
            ('y', 3)],
           )

mergejoin

merge函数

merge函数的作用是将两个pandas对象横向合并,遇到重复的索引项时会使用笛卡尔积,默认inner连接,可选leftouterright连接
所谓左连接,就是指以第一个表索引为基准,右边的表中如果不再左边的则不加入,如果在左边的就以笛卡尔积的方式加入
merge/joinconcat的不同之处在于on参数,可以指定某一个对象为key来进行连接
同样的,下面举一些例子:

left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
                     'key2': ['K0', 'K1', 'K0', 'K1'],
                      'A': ['A0', 'A1', 'A2', 'A3'],
                      'B': ['B0', 'B1', 'B2', 'B3']}) 
right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
                      'key2': ['K0', 'K0', 'K0', 'K0'],
                      'C': ['C0', 'C1', 'C2', 'C3'],
                      'D': ['D0', 'D1', 'D2', 'D3']})
right2 = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
                      'key2': ['K0', 'K0', 'K0', 'K0'],
                      'C': ['C0', 'C1', 'C2', 'C3']})

key1为准则连接,如果具有相同的列,则默认suffixes=('_x','_y')

pd.merge(left, right, on='key1')


以多组键连接:

pd.merge(left, right, on=['key1','key2'])


默认使用inner连接,因为merge只能横向拼接,所以取行向上keys的交集,下面看如果使用how=outer参数
注意:这里的how就是concatjoin

pd.merge(left, right, how='outer', on=['key1','key2'])


左连接:

pd.merge(left, right, how='left', on=['key1', 'key2'])


右连接:

pd.merge(left, right, how='right', on=['key1', 'key2'])


如果还是对笛卡尔积不太了解,请务必理解下面这个例子,由于B的所有元素为2,因此需要6行:

left = pd.DataFrame({'A': [1, 2], 'B': [2, 2]})
right = pd.DataFrame({'A': [4, 5, 6], 'B': [2, 2, 2]})
pd.merge(left, right, on='B', how='outer')

validate检验的是到底哪一边出现了重复索引,如果是“one_to_one”则两侧索引都是唯一,如果"one_to_many"则左侧唯一

left = pd.DataFrame({'A': [1, 2], 'B': [2, 2]})
right = pd.DataFrame({'A': [4, 5, 6], 'B': [2, 3, 4]})
#pd.merge(left, right, on='B', how='outer',validate='one_to_one') #报错
left = pd.DataFrame({'A': [1, 2], 'B': [2, 1]})
pd.merge(left, right, on='B', how='outer',validate='one_to_one')


indicator参数指示了,合并后该行索引的来源

df1 = pd.DataFrame({'col1': [0, 1], 'col_left': ['a', 'b']})
df2 = pd.DataFrame({'col1': [1, 2, 2], 'col_right': [2, 2, 2]})
pd.merge(df1, df2, on='col1', how='outer', indicator=True) #indicator='indicator_column'也是可以的

join函数

join函数作用是将多个pandas对象横向拼接,遇到重复的索引项时会使用笛卡尔积,默认左连接,可选innerouterright连接

left = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
                     'B': ['B0', 'B1', 'B2']},
                    index=['K0', 'K1', 'K2'])
right = pd.DataFrame({'C': ['C0', 'C2', 'C3'],
                      'D': ['D0', 'D2', 'D3']},
                    index=['K0', 'K2', 'K3'])
left.join(right)


对于many_to_one模式下的合并,往往join更为方便
同样可以指定key

left = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                     'B': ['B0', 'B1', 'B2', 'B3'],
                     'key': ['K0', 'K1', 'K0', 'K1']})
right = pd.DataFrame({'C': ['C0', 'C1'],
                      'D': ['D0', 'D1']},
                     index=['K0', 'K1'])
left.join(right, on='key')


多层key

left = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                     'B': ['B0', 'B1', 'B2', 'B3'],
                     'key1': ['K0', 'K0', 'K1', 'K2'],
                     'key2': ['K0', 'K1', 'K0', 'K1']})
index = pd.MultiIndex.from_tuples([('K0', 'K0'), ('K1', 'K0'),
                                   ('K2', 'K0'), ('K2', 'K1')],names=['key1','key2'])
right = pd.DataFrame({'C': ['C0', 'C1', 'C2', 'C3'],
                      'D': ['D0', 'D1', 'D2', 'D3']},
                     index=index)
left.join(right, on=['key1','key2'])

练习

练习一

有2张公司的员工信息表,每个公司共有16名员工,共有五个公司,请解决如下问题

pd.read_csv('data/Employee1.csv').head()
pd.read_csv('data/Employee2.csv').head()
  • 每个公司有多少员工满足如下条件:既出现第一张表,又出现在第二张表。
L = list(set(df1['Name']).intersection(set(df2['Name'])))
L
['b1',
 'b3',
 'c13',
 'a6',
 'd10',
 'b15',
 'c10',
 'c12',
 'b7',
 'e10',
 'e11',
 'd5',
 'a1',
 'a3',
 'c3',
 'e8']
  • 将所有不符合1中条件的行筛选出来,合并为一张新表,列名与原表一致。
df_b1 = df1[~df1['Name'].isin(L)]
df_b2 = df2[~df2['Name'].isin(L)]
df_b = pd.concat([df_b1,df_b2]).set_index('Name')
df_b.head()
  • 现在需要编制所有80位员工的信息表,对于2中的员工要求不变,对于满足1条件员工,它们在某个指标的数值,取偏离它所属公司中满足b员工的均值数较小的哪一个,例如:P公司在两张表的交集为{p1},并集扣除交集为{p2,p3,p4},那么如果后者集合的工资均值为1万元,且p1在表1的工资为13000元,在表2的工资为9000元,那么应该最后取9000元作为p1的工资,最后对于没有信息的员工,利用缺失值填充。
df2 = pd.read_csv('data/Employee2.csv')
df1['重复'] = ['Y_1' if df1.loc[i,'Name'] in L else 'N' for i in range(df1.shape[0])]
df2['重复'] = ['Y_2' if df2.loc[i,'Name'] in L else 'N' for i in range(df2.shape[0])]
df1 = df1.set_index(['Name','重复'])
df2 = df2.set_index(['Name','重复'])
df_c = pd.concat([df1,df2])
result = pd.DataFrame({'Company':[],'Name':[],'Age':[],'Height':[],'Weight':[],'Salary':[]})
group = df_c.groupby(['Company','重复'])
for i in L:
    first = group.get_group((i[0].upper(),'Y_1')).reset_index(level=1).loc[i,:][-4:]
    second = group.get_group((i[0].upper(),'Y_2')).reset_index(level=1).loc[i,:][-4:]
    mean = group.get_group((i[0].upper(),'N')).reset_index(level=1).mean()
    final = [i[0].upper(),i]
    for j in range(4):
        final.append(first[j] if abs(first[j]-mean[j])<abs(second[j]-mean[j]) else second[j])
    result = pd.concat([result,pd.DataFrame({result.columns.tolist()[k]:[final[k]] for k in range(6)})])
result = pd.concat([result.set_index('Name'),df_b])
for i in list('abcde'):
    for j in range(1,17):
        item = i+str(j)
        if item not in result.index:
            result = pd.concat([result,pd.DataFrame({'Company':[i.upper()],'Name':[item]
                 ,'Age':[np.nan],'Height':[np.nan],'Weight':[np.nan],'Salary':[np.nan]}).set_index('Name')])
result['Number'] = [int(i[1:]) for i in result.index]
result.reset_index().drop(columns='Name').set_index(['Company','Number']).sort_index()

练习二

有2张课程的分数表(分数随机生成),但专业课(学科基础课、专业必修课、专业选修课)与其他课程混在一起,请解决如下问题:

pd.read_csv('data/Course1.csv').head()
pd.read_csv('data/Course2.csv').head()


  • 将两张表分别拆分为专业课与非专业课(结果为四张表)。
df1 = pd.read_csv('data/Course1.csv')
df2 = pd.read_csv('data/Course2.csv')
df_a11,df_a12,df_a21,df_a22 =0,0,0,0
df_a11= df1.query('课程类别 in ["学科基础课","专业必修课","专业选修课"]')
df_a12= df1.query('课程类别 not in ["学科基础课","专业必修课","专业选修课"]')
df_a21= df2.query('课程类别 in ["学科基础课","专业必修课","专业选修课"]')
df_a22= df2.query('课程类别 not in ["学科基础课","专业必修课","专业选修课"]')
df_a11.head()
  • 将两张专业课的分数表和两张非专业课的分数表分别合并。
special = pd.concat([df_a11,df_a21])
common = pd.concat([df_a12,df_a22])
special.query('课程类别 not in ["学科基础课","专业必修课","专业选修课"]')
common.query('课程类别 in ["学科基础课","专业必修课","专业选修课"]')
  • 不使用1中的步骤,请直接读取两张表合并后拆分。
df = pd.concat([df1,df2])
special2 = df.query('课程类别 in ["学科基础课","专业必修课","专业选修课"]')
common2 = df.query('课程类别 not in ["学科基础课","专业必修课","专业选修课"]')
(special.equals(special2),common.equals(common2))
(True, True)
  • 专业课程中有缺失值吗,如果有的话请在完成3的同时,用组内(3种类型的专业课)均值填充缺失值后拆分。
df['分数'] = df.groupby('课程类别').transform(lambda x: x.fillna(x.mean()))['分数']
df.isnull().all()
课程名字    False
课程类别    False
学分      False
分数      False
dtype: bool
special3 = df.query('课程类别 in ["学科基础课","专业必修课","专业选修课"]')
common3 = df.query('课程类别 not in ["学科基础课","专业必修课","专业选修课"]')
common3.head()

缺失数据

import pandas as pd
import numpy as np
df = pd.read_csv('data/table_missing.csv')
df.head()

缺失观测及其类型

了解缺失信息

isnanotna方法

Series使用会返回布尔列表

df['Physics'].isna().head()
0    False
1    False
2    False
3     True
4    False
Name: Physics, dtype: bool
df['Physics'].notna().head()
0     True
1     True
2     True
3    False
4     True
Name: Physics, dtype: bool

DataFrame使用会返回布尔表

df.isna().head()


但对于DataFrame我们更关心到底每列有多少缺失值

df.isna().sum()
Out[5]:
School      0
Class       4
ID          6
Gender      7
Address     0
Height      0
Weight     13
Math        5
Physics     4
dtype: int64
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 35 entries, 0 to 34
Data columns (total 9 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   School   35 non-null     object 
 1   Class    31 non-null     object 
 2   ID       29 non-null     float64
 3   Gender   28 non-null     object 
 4   Address  35 non-null     object 
 5   Height   35 non-null     int64  
 6   Weight   22 non-null     float64
 7   Math     30 non-null     float64
 8   Physics  31 non-null     object 
dtypes: float64(3), int64(1), object(5)
memory usage: 2.6+ KB

查看缺失值的所以在行

以最后一列为例,挑出该列缺失值的行

df[df['Physics'].isna()]

挑选出所有非缺失值列

使用all就是全部非缺失值,如果是any就是至少有一个不是缺失值

df[df.notna().all(1)]

三种缺失符号

np.nan

np.nan是一个麻烦的东西,首先它不等与任何东西,甚至不等于自己

np.nan == np.nan
False
np.nan == 0
False
np.nan == None
False

在用equals函数比较时,自动略过两侧全是np.nan的单元格,因此结果不会影响

df.equals(df)
True

其次,它在numpy中的类型为浮点,由此导致数据集读入时,即使原来是整数的列,只要有缺失值就会变为浮点型

type(np.nan)
float
pd.Series([1,2,3]).dtype
dtype('int64')
pd.Series([1,np.nan,3]).dtype
dtype('float64')

此外,对于布尔类型的列表,如果是np.nan填充,那么它的值会自动变为True而不是False

pd.Series([1,np.nan,3],dtype='bool')
0    True
1    True
2    True
dtype: bool

但当修改一个布尔列表时,会改变列表类型,而不是赋值为True

s = pd.Series([True,False],dtype='bool')
s
0    1.0
1    NaN
dtype: float64

在所有的表格读取后,无论列是存放什么类型的数据,默认的缺失值全为np.nan类型
因此整型列转为浮点;而字符由于无法转化为浮点,因此只能归并为object类型('O'),原来是浮点型的则类型不变

df['ID'].dtype
dtype('float64')
df['Math'].dtype
dtype('float64')
df['Class'].dtype
dtype('O')

None

None比前者稍微好些,至少它会等于自身

None == None
True

它的布尔值为False

pd.Series([None],dtype='bool')
0    False
dtype: bool

修改布尔列表不会改变数据类型

s = pd.Series([True,False],dtype='bool')
s[0]=None
s
0    False
1    False
dtype: bool
s = pd.Series([1,0],dtype='bool')
s
0    False
1    False
dtype: bool

在传入数值类型后,会自动变为np.nan

type(pd.Series([1,None])[1])
numpy.float64

只有当传入object类型是保持不动,几乎可以认为,除非人工命名None,它基本不会自动出现在Pandas

type(pd.Series([1,None],dtype='O')[1])
NoneType

在使用equals函数时不会被略过,因此下面的情况下返回False

pd.Series([None]).equals(pd.Series([np.nan]))
False

NaT

NaT是针对时间序列的缺失值,是Pandas的内置类型,可以完全看做时序版本的np.nan,与自己不等,且使用equals时也会被跳过

s_time = pd.Series([pd.Timestamp('20120101')]*5)
s_time
0   2012-01-01
1   2012-01-01
2   2012-01-01
3   2012-01-01
4   2012-01-01
dtype: datetime64[ns]
s_time[2] = None
s_time
0   2012-01-01
1   2012-01-01
2          NaT
3   2012-01-01
4   2012-01-01
dtype: datetime64[ns]
s_time[2] = np.nan
s_time
0   2012-01-01
1   2012-01-01
2          NaT
3   2012-01-01
4   2012-01-01
dtype: datetime64[ns]
s_time[2] = pd.NaT
s_time
0   2012-01-01
1   2012-01-01
2          NaT
3   2012-01-01
4   2012-01-01
dtype: datetime64[ns]
type(s_time[2])
pandas._libs.tslibs.nattype.NaTType
s_time[2] == s_time[2]
False
s_time.equals(s_time)
True
s = pd.Series([True,False],dtype='bool')
s[1]=pd.NaT
s
0    True
1    True
dtype: bool

Nullable类型与NA符号

官方鼓励用户使用新的数据类型和缺失类型pd.NA

Nullable整形

对于该种类型而言,它与原来标记int上的符号区别在于首字母大写:'Int'

s_original = pd.Series([1, 2], dtype="int64")
s_original
0    1
1    2
dtype: int64
s_new = pd.Series([1, 2], dtype="Int64")
s_new
0    1
1    2
dtype: Int64

它的好处就在于,其中前面提到的三种缺失值都会被替换为统一的NA符号,且不改变数据类型

s_original[1] = np.nan
s_original
0    1.0
1    NaN
dtype: float64
s_new[1] = np.nan
s_new
0       1
1    <NA>
dtype: Int64
s_new[1] = None
Out[40]:
0       1
1    <NA>
dtype: Int64
s_new[1] = pd.NaT
s_new
0       1
1    <NA>
dtype: Int64

Nullable布尔

对于该种类型而言,作用与上面的类似,记号为boolean

s_original = pd.Series([1, 0], dtype="bool")
s_original
0     True
1    False
dtype: bool
s_new = pd.Series([0, 1], dtype="boolean")
s_new
0    False
1     True
dtype: boolean
s_original[0] = np.nan
s_original
0    NaN
1    0.0
dtype: float64
s_original = pd.Series([1, 0], dtype="bool") #此处重新加一句是因为前面赋值改变了bool类型
s_original[0] = None
s_original
0    False
1    False
dtype: bool
s_new[0] = np.nan
s_new
0    <NA>
1    True
dtype: boolean
s_new[0] = None
s_new
0    <NA>
1    True
dtype: boolean
s_new[0] = pd.NaT
s_new
0    <NA>
1    True
dtype: boolean
s = pd.Series(['dog','cat'])
s[s_new]
1    cat
dtype: object

string类型

它本质上也属于Nullable类型,因为并不会因为含有缺失而改变类型

s = pd.Series(['dog','cat'],dtype='string')
s
0    dog
1    cat
dtype: string
s[0] = np.nan
s
0    <NA>
1     cat
dtype: string
s[0] = None
s
0    <NA>
1     cat
dtype: string
s[0] = pd.NaT
s
0    <NA>
1     cat
dtype: string

此外,和object类型的一点重要区别就在于,在调用字符方法后,string类型返回的是Nullable类型,object则会根据缺失类型和数据类型而改变

s = pd.Series(["a", None, "b"], dtype="string")
s.str.count('a')
0       1
1    <NA>
2       0
dtype: Int64
s2 = pd.Series(["a", None, "b"], dtype="object")
s2.str.count("a")
0    1.0
1    NaN
2    0.0
dtype: float64
s.str.isdigit()
0    False
1     <NA>
2    False
dtype: boolean
s2.str.isdigit()
0    False
1     None
2    False
dtype: object

NA的特性

逻辑运算

只需看该逻辑运算的结果是否依赖pd.NA的取值,如果依赖,则结果还是NA,如果不依赖,则直接计算结果

True | pd.NA
True
pd.NA | True
True
False | pd.NA
<NA>
False & pd.NA
False
True & pd.NA
<NA>

取值不明直接报错

#bool(pd.NA)

算术运算和比较运算

这里只需记住除了下面两类情况,其他结果都是NA即可

pd.NA ** 0
1
1 ** pd.NA
1
pd.NA + 1
<NA>
"a" * pd.NA
<NA>
pd.NA == pd.NA
<NA>
pd.NA < 2.5
<NA>
np.log(pd.NA)
<NA>
np.add(pd.NA, 1)
<NA>

convert_dtypes方法

这个函数的功能往往就是在读取数据时,就把数据列转为Nullable类型

pd.read_csv('data/table_missing.csv').dtypes
School      object
Class       object
ID         float64
Gender      object
Address     object
Height       int64
Weight     float64
Math       float64
Physics     object
dtype: object
pd.read_csv('data/table_missing.csv').convert_dtypes().dtypes
School      string
Class       string
ID           Int64
Gender      string
Address     string
Height       Int64
Weight       Int64
Math       float64
Physics     string
dtype: object

缺失数据的运算与分组

加号与乘号规则

使用加法时,缺失值为0

s = pd.Series([2,3,np.nan,4])
s.sum()
9.0

使用乘法时,缺失值为1

s.prod()
24.0

使用累计函数时,缺失值自动略过

s.cumsum()
0    2.0
1    5.0
2    NaN
3    9.0
dtype: float64
s.cumprod()
0     2.0
1     6.0
2     NaN
3    24.0
dtype: float64
s.pct_change()
0         NaN
1    0.500000
2    0.000000
3    0.333333
dtype: float64

groupby方法中的缺失值

自动忽略为缺失值的组

df_g = pd.DataFrame({'one':['A','B','C','D',np.nan],'two':np.random.randn(5)})
df_g
df_g.groupby('one').groups
{'A': Int64Index([0], dtype='int64'),
 'B': Int64Index([1], dtype='int64'),
 'C': Int64Index([2], dtype='int64'),
 'D': Int64Index([3], dtype='int64')}

填充与剔除

fillna方法

值填充与前后向填充(分别与ffill方法和bfill方法等价)

df['Physics'].fillna('missing').head()
0         A+
1         B+
2         B+
3    missing
4         A-
Name: Physics, dtype: object
df['Physics'].fillna(method='ffill').head()
0    A+
1    B+
2    B+
3    B+
4    A-
Name: Physics, dtype: object
df['Physics'].fillna(method='backfill').head()
0    A+
1    B+
2    B+
3    A-
4    A-
Name: Physics, dtype: object

填充中的对齐特性

df_f = pd.DataFrame({'A':[1,3,np.nan],'B':[2,4,np.nan],'C':[3,5,np.nan]})
df_f.fillna(df_f.mean())


返回的结果中没有C,根据对齐特点不会被填充

df_f.fillna(df_f.mean()[['A','B']])

dropna方法

axis参数

df_d = pd.DataFrame({'A':[np.nan,np.nan,np.nan],'B':[np.nan,3,2],'C':[3,2,1]})
df_d
df_d.dropna(axis=0)
df_d.dropna(axis=1)

how参数(可以选all或者any,表示全为缺失去除和存在缺失去除)

df_d.dropna(axis=1,how='all')

subset参数(即在某一组列范围中搜索缺失值)

df_d.dropna(axis=0,subset=['B','C'])

插值(interpolation

线性插值

索引无关的线性插值

默认状态下,interpolate会对缺失的值进行线性插值

s = pd.Series([1,10,15,-5,-2,np.nan,np.nan,28])
s
0     1.0
1    10.0
2    15.0
3    -5.0
4    -2.0
5     NaN
6     NaN
7    28.0
dtype: float64
s.interpolate()
0     1.0
1    10.0
2    15.0
3    -5.0
4    -2.0
5     8.0
6    18.0
7    28.0
dtype: float64
s.interpolate().plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7df20af50>


此时的插值与索引无关

s.index = np.sort(np.random.randint(50,300,8))
s.interpolate()
#值不变
69      1.0
71     10.0
84     15.0
117    -5.0
119    -2.0
171     8.0
219    18.0
236    28.0
dtype: float64
s.interpolate().plot()
#后面三个点不是线性的(如果几乎为线性函数,请重新运行上面的一个代码块,这是随机性导致的)
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dfc69890>

与索引有关的插值

method中的indextime选项可以使插值线性地依赖索引,即插值为索引的线性函数

s.interpolate(method='index').plot()
#可以看到与上面的区别
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dca0c4d0>


如果索引是时间,那么可以按照时间长短插值

s_t = pd.Series([0,np.nan,10]
        ,index=[pd.Timestamp('2012-05-01'),pd.Timestamp('2012-05-07'),pd.Timestamp('2012-06-03')])
s_t
2012-05-01     0.0
2012-05-07     NaN
2012-06-03    10.0
dtype: float64
s_t.interpolate().plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dc964850>
s_t.interpolate(method='time').plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dc8eda10>

interpolate中的限制参数

limit表示最多插入多少个

s = pd.Series([1,np.nan,np.nan,np.nan,5])
s.interpolate(limit=2)
0    1.0
1    2.0
2    3.0
3    NaN
4    5.0
dtype: float64

limit_direction表示插值方向,可选forward,backward,both,默认前向

s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
s.interpolate(limit_direction='backward')
0    1.0
1    1.0
2    1.0
3    2.0
4    3.0
5    4.0
6    5.0
7    NaN
8    NaN
dtype: float64

limit_area表示插值区域,可选inside,outside,默认None

s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
s.interpolate(limit_area='inside')
0    NaN
1    NaN
2    1.0
3    2.0
4    3.0
5    4.0
6    5.0
7    NaN
8    NaN
dtype: float64
s = pd.Series([np.nan,np.nan,1,np.nan,np.nan,np.nan,5,np.nan,np.nan,])
s.interpolate(limit_area='outside')
0    NaN
1    NaN
2    1.0
3    NaN
4    NaN
5    NaN
6    5.0
7    5.0
8    5.0
dtype: float64

练习

练习一

现有一份虚拟数据集,列类型分别为string/浮点/整型,请解决如下问题

pd.read_csv('data/Missing_data_one.csv').head()
  • 请以列类型读入数据,并选出C为缺失值的行。
df[df['C'].isna()]
  • 现需要将A中的部分单元转为缺失值,单元格中的最小转换概率为25%,且概率大小与所在行B列单元的值成正比。
df = pd.read_csv('data/Missing_data_one.csv').convert_dtypes()
total_b = df['B'].sum()
min_b = df['B'].min()
df['A'] = pd.Series(list(zip(df['A'].values
                    ,df['B'].values))).apply(lambda x:x[0] if np.random.rand()>0.25*x[1]/min_b else np.nan)
df.head()

练习二

现有一份缺失的数据集,记录了36个人来自的地区、身高、体重、年龄和工资,请解决如下问题:

pd.read_csv('data/Missing_data_two.csv').head()
  • 统计各列缺失的比例并选出在后三列中至少有两个非缺失值的行。
df.isna().sum()/df.shape[0]
Out[60]:
编号    0.000000
地区    0.000000
身高    0.000000
体重    0.222222
年龄    0.250000
工资    0.222222
dtype: float64
df_not2na = df[df.iloc[:,-3:].isna().sum(1)<=1]
df_not2na.head()
  • 请结合身高列和地区列中的数据,对体重进行合理插值。
    分地区,用排序后的身高信息进行线性插值
df_method_1 = df.copy()
for name,group in df_method_1.groupby('地区'):
    df_method_1.loc[group.index,'体重'] = group[['身高','体重']].sort_values(by='身高').interpolate()['体重']
df_method_1['体重'] = df_method_1['体重'].round(decimals=2)
df_method_1.head()

文本数据

import pandas as pd
import numpy as np

string类型的性质

stringobject的区别

string类型和object不同之处有三:

  1. 字符存取方法(string accessor methods,如str.count)会返回相应数据的Nullable类型,而object会随缺失值的存在而改变返回类型
  2. 某些Series方法不能在string上使用,例如: Series.str.decode(),因为存储的是字符串而不是字节
  3. string类型在缺失值存储或运算时,类型会广播为pd.NA,而不是浮点型np.nan
    其余全部内容在当前版本下完全一致,但迎合Pandas的发展模式,我们仍然全部用string来操作字符串

string类型的转换

如果将一个其他类型的容器直接转换string类型可能会出错:

#pd.Series([1,'1.']).astype('string') #报错
#pd.Series([1,2]).astype('string') #报错
#pd.Series([True,False]).astype('string') #报错

当下正确的方法是分两部转换,先转为strobject,在转为string类型:

pd.Series([1,'1.']).astype('str').astype('string')
0     1
1    1.
dtype: string
pd.Series([1,2]).astype('str').astype('string')
0    1
1    2
dtype: string
pd.Series([True,False]).astype('str').astype('string')
0     True
1    False
dtype: string

拆分与拼接

str.split方法

分割符与str的位置元素选取

s = pd.Series(['a_b_c', 'c_d_e', np.nan, 'f_g_h'], dtype="string")
s
0    a_b_c
1    c_d_e
2     <NA>
3    f_g_h
dtype: string

根据某一个元素分割,默认为空格

s.str.split('_')
0    [a, b, c]
1    [c, d, e]
2         <NA>
3    [f, g, h]
dtype: object

这里需要注意split后的类型是object,因为现在Series中的元素已经不是string,而包含了list,且string类型只能含有字符串
对于str方法可以进行元素的选择,如果该单元格元素是列表,那么str[i]表示取出第i个元素,如果是单个元素,则先把元素转为列表在取出

s.str.split('_').str[1]
0       b
1       d
2    <NA>
3       g
dtype: object
pd.Series(['a_b_c', ['a','b','c']], dtype="object").str[1]
#第一个元素先转为['a','_','b','_','c']
0    _
1    b
dtype: object

其他参数

expand参数控制了是否将列拆开,n参数代表最多分割多少次

s.str.split('_',expand=True)
s.str.split('_',n=1)
0    [a, b_c]
1    [c, d_e]
2        <NA>
3    [f, g_h]
dtype: object
s.str.split('_',expand=True,n=1)

str.cat方法

不同对象的拼接模式

cat方法对于不同对象的作用结果并不相同,其中的对象包括:单列、双列、多列

对于单个Series而言,就是指所有的元素进行字符合并为一个字符串
s = pd.Series(['ab',None,'d'],dtype='string')
s
0      ab
1    <NA>
2       d
dtype: string
s.str.cat()
'abd'

其中可选sep分隔符参数,和缺失值替代字符na_rep参数

s.str.cat(sep=',')
'ab,d'
s.str.cat(sep=',',na_rep='*')
'ab,*,d'
对于两个Series合并而言,是对应索引的元素进行合并
s2 = pd.Series(['24',None,None],dtype='string')
s2
0      24
1    <NA>
2    <NA>
dtype: string
s.str.cat(s2)
0    ab24
1    <NA>
2    <NA>
dtype: string

同样也有相应参数,需要注意的是两个缺失值会被同时替换

s.str.cat(s2,sep=',',na_rep='*')
0    ab,24
1      *,*
2      d,*
dtype: string
多列拼接可以分为表的拼接和多Series拼接

表的拼接

s.str.cat(pd.DataFrame({0:['1','3','5'],1:['5','b',None]},dtype='string'),na_rep='*')
0    ab15
1     *3b
2     d5*
dtype: string

多个Series拼接

s.str.cat([s+'0',s*2])
0    abab0abab
1         <NA>
2        dd0dd
dtype: string
cat中的索引对齐

当前版本中,如果两边合并的索引不相同且未指定join参数,默认为左连接,设置join='left'

s2 = pd.Series(list('abc'),index=[1,2,3],dtype='string')
s2
1    a
2    b
3    c
dtype: string
s.str.cat(s2,na_rep='*')
0    ab*
1     *a
2     db
dtype: string

替换

广义上的替换,就是指str.replace函数的应用,fillna是针对缺失值的替换
提到替换,就不可避免地接触到正则表达式,这里默认读者已掌握常见正则表达式知识点,若对其还不了解的,可以通过这份资料来熟悉

str.replace的常见用法

s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca','', np.nan, 'CABA', 'dog', 'cat'],dtype="string")
s
0       A
1       B
2       C
3    Aaba
4    Baca
5        
6    <NA>
7    CABA
8     dog
9     cat
dtype: string

第一个值写r开头的正则表达式,后一个写替换的字符串

s.str.replace(r'^[AB]','***')
0       ***
1       ***
2         C
3    ***aba
4    ***aca
5          
6      <NA>
7      CABA
8       dog
9       cat
dtype: string

子组与函数替换

通过正整数调用子组(0返回字符本身,从1开始才是子组)

s.str.replace(r'([ABC])(\w+)',lambda x:x.group(2)[1:]+'*')
0       A
1       B
2       C
3     ba*
4     ca*
5        
6    <NA>
7     BA*
8     dog
9     cat
dtype: string

利用?P<....>表达式可以对子组命名调用

s.str.replace(r'(?P<one>[ABC])(?P<two>\w+)',lambda x:x.group('two')[1:]+'*')
0       A
1       B
2       C
3     ba*
4     ca*
5        
6    <NA>
7     BA*
8     dog
9     cat
dtype: string

关于str.replace的注意事项

首先,要明确str.replacereplace并不是一个东西:
str.replace针对的是object类型或string类型,默认是以正则表达式为操作,目前暂时不支持DataFrame上使用
replace针对的是任意类型的序列或数据框,如果要以正则表达式替换,需要设置regex=True,该方法通过字典可支持多列替换
但现在由于string类型的初步引入,用法上出现了一些问题,这些issue有望在以后的版本中修复

str.replace赋值参数不得为pd.NA

这听上去非常不合理,例如对满足某些正则条件的字符串替换为缺失值,直接更改为缺失值在当下版本就会报错

#pd.Series(['A','B'],dtype='string').str.replace(r'[A]',pd.NA) #报错
#pd.Series(['A','B'],dtype='O').str.replace(r'[A]',pd.NA) #报错

此时,可以先转为object类型再转换回来,曲线救国:

pd.Series(['A','B'],dtype='string').astype('O').replace(r'[A]',pd.NA,regex=True).astype('string')
0    <NA>
1       B
dtype: string

至于为什么不用replace函数的regex替换(但string类型replace的非正则替换是可以的),原因在下面一条

对于string类型Series,在使用replace函数时不能使用正则表达式替换

该bug现在还未修复

pd.Series(['A','B'],dtype='string').replace(r'[A]','C',regex=True)
0    A
1    B
dtype: string
pd.Series(['A','B'],dtype='O').replace(r'[A]','C',regex=True)
0    C
1    B
dtype: object

string类型序列如果存在缺失值,不能使用replace替换

#pd.Series(['A',np.nan],dtype='string').replace('A','B') #报错
pd.Series(['A',np.nan],dtype='string').str.replace('A','B')
0       B
1    <NA>
dtype: string

综上,概况的说,除非需要赋值元素为缺失值(转为object再转回来),否则请使用str.replace方法

子串匹配与提取

str.extract方法

常见用法

pd.Series(['10-87', '10-88', '10-89'],dtype="string").str.extract(r'([\d]{2})-([\d]{2})')


使用子组名作为列名

pd.Series(['10-87', '10-88', '-89'],dtype="string").str.extract(r'(?P<name_1>[\d]{2})-(?P<name_2>[\d]{2})')


利用?正则标记选择部分提取

pd.Series(['10-87', '10-88', '-89'],dtype="string").str.extract(r'(?P<name_1>[\d]{2})?-(?P<name_2>[\d]{2})')
pd.Series(['10-87', '10-88', '10-'],dtype="string").str.extract(r'(?P<name_1>[\d]{2})-(?P<name_2>[\d]{2})?')

expand参数(默认为True

对于一个子组的Series,如果expand设置为False,则返回Series,若大于一个子组,则expand参数无效,全部返回DataFrame
对于一个子组的Index,如果expand设置为False,则返回提取后的Index,若大于一个子组且expandFalse,报错

s.index
Index(['A11', 'B22', 'C33'], dtype='object')
s.str.extract(r'([\w])')
        0
A11	a
B22	b
C33	c
s.str.extract(r'([\w])',expand=False)
A11    a
B22    b
C33    c
dtype: string
s.index.str.extract(r'([\w])')
        0
0	A
1	B
2	C
s.index.str.extract(r'([\w])',expand=False)
Index(['A', 'B', 'C'], dtype='object')
s.index.str.extract(r'([\w])([\d])')
        0	1
0	A	1
1	B	2
2	C	3
#s.index.str.extract(r'([\w])([\d])',expand=False) #报错

str.extractall方法

extract只匹配第一个符合条件的表达式不同,extractall会找出所有符合条件的字符串,并建立多级索引(即使只找到一个)

s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"],dtype="string")
two_groups = '(?P<letter>[a-z])(?P<digit>[0-9])'
s.str.extract(two_groups, expand=True)
s.str.extractall(two_groups)
s['A']='a1'
s.str.extractall(two_groups)


如果想查看第i层匹配,可使用xs方法

s = pd.Series(["a1a2", "b1b2", "c1c2"], index=["A", "B", "C"],dtype="string")
s.str.extractall(two_groups).xs(1,level='match')

str.containsstr.match

前者的作用为检测是否包含某种正则模式

pd.Series(['1', None, '3a', '3b', '03c'], dtype="string").str.contains(r'[0-9][a-z]')
Out[49]:
0    False
1     <NA>
2     True
3     True
4     True
dtype: boolean

可选参数为na

pd.Series(['1', None, '3a', '3b', '03c'], dtype="string").str.contains('a', na=False)
0    False
1    False
2     True
3    False
4    False
dtype: boolean

str.match与其区别在于,match依赖于pythonre.match,检测内容为是否从头开始包含该正则模式

pd.Series(['1', None, '3a_', '3b', '03c'], dtype="string").str.match(r'[0-9][a-z]',na=False)
0    False
1    False
2     True
3     True
4    False
dtype: boolean
pd.Series(['1', None, '_3a', '3b', '03c'], dtype="string").str.match(r'[0-9][a-z]',na=False)
0    False
1    False
2    False
3     True
4    False
dtype: boolean

常用字符串方法

过滤型方法

str.strip

常用于过滤空格

pd.Series(list('abc'),index=[' space1  ','space2  ','  space3'],dtype="string").index.str.strip()
Index(['space1', 'space2', 'space3'], dtype='object')

str.lowerstr.upper

pd.Series('A',dtype="string").str.lower()
0    a
dtype: string
pd.Series('a',dtype="string").str.upper()
0    A
dtype: string

str.swapcasestr.capitalize

分别表示交换字母大小写和大写首字母

pd.Series('abCD',dtype="string").str.swapcase()
0    ABcd
dtype: string
pd.Series('abCD',dtype="string").str.capitalize()
0    Abcd
dtype: string

isnumeric方法

检查每一位是否都是数字,请问如何判断是否是数值?(问题二)

pd.Series(['1.2','1','-0.3','a',np.nan],dtype="string").str.isnumeric()
0    False
1     True
2    False
3    False
4     <NA>
dtype: boolean

练习

练习一

现有一份关于字符串的数据集,请解决以下问题:

pd.read_csv('data/String_data_one.csv',index_col='人员编号').head()
  • 现对字符串编码存储人员信息(在编号后添加ID列),使用如下格式:“×××(名字):×国人,性别×,生于×年×月×日”
(df['姓名']+':'+df['国籍']+'国人,性别'
          +df['性别']+',生于'
          +df['出生年']+'年'
          +df['出生月']+'月'+df['出生日']+'日').to_frame().rename(columns={0:'ID'}).head()
  • 将1中的人员生日信息部分修改为用中文表示(如一九七四年十月二十三日),其余返回格式不变。
L_year = list('零一二三四五六七八九')
L_one = [s.strip() for s in list('  二三四五六七八九')]
L_two = [s.strip() for s in list(' 一二三四五六七八九')]
df_new = (df['姓名']+':'+df['国籍']+'国人,性别'+df['性别']+',生于'
          +df['出生年'].str.replace(r'\d',lambda x:L_year[int(x.group(0))])+'年'
          +df['出生月'].apply(lambda x:x if len(x)==2 else '0'+x)\
                      .str.replace(r'(?P<one>[\d])(?P<two>\d?)',lambda x:L_one[int(x.group('one'))]
                      +bool(int(x.group('one')))*'十'+L_two[int(x.group('two'))])+'月'
          +df['出生日'].apply(lambda x:x if len(x)==2 else '0'+x)\
                      .str.replace(r'(?P<one>[\d])(?P<two>\d?)',lambda x:L_one[int(x.group('one'))]
                      +bool(int(x.group('one')))*'十'+L_two[int(x.group('two'))])+'日')\
          .to_frame().rename(columns={0:'ID'})
df_new.head()
  • 将2中的ID列结果拆分为原列表相应的5列,并使用equals检验是否一致。
dic_year = {i[0]:i[1] for i in zip(list('零一二三四五六七八九'),list('0123456789'))}
dic_two = {i[0]:i[1] for i in zip(list('十一二三四五六七八九'),list('0123456789'))}
dic_one = {'十':'1','二十':'2','三十':'3',None:''}
df_res = df_new['ID'].str.extract(r'(?P<姓名>[a-zA-Z]+):(?P<国籍>[\d])国人,性别(?P<性别>[\w]),生于(?P<出生年>[\w]{4})年(?P<出生月>[\w]+)月(?P<出生日>[\w]+)日')
df_res['出生年'] = df_res['出生年'].str.replace(r'(\w)+',lambda x:''.join([dic_year[x.group(0)[i]] for i in range(4)]))
df_res['出生月'] = df_res['出生月'].str.replace(r'(?P<one>\w?十)?(?P<two>[\w])',lambda x:dic_one[x.group('one')]+dic_two[x.group('two')]).str.replace(r'0','10')
df_res['出生日'] = df_res['出生日'].str.replace(r'(?P<one>\w?十)?(?P<two>[\w])',lambda x:dic_one[x.group('one')]+dic_two[x.group('two')]).str.replace(r'^0','10')
df_res.head()
df_res.equals(df)

练习二

现有一份半虚拟的数据集,第一列包含了新型冠状病毒的一些新闻标题,请解决以下问题:

pd.read_csv('data/String_data_two.csv').head()
  • 选出所有关于北京市和上海市新闻标题的所在行。
df[df['col1'].str.contains(r'[北京]{2}|[上海]{2}')].head()
  • 求col2的均值。
#df['col2'].mean() #报错
df['col2'][~(df['col2'].str.replace(r'-?\d+','True')=='True')] #这三行有问题
309    0-
396    9`
485    /7
Name: col2, dtype: object
df.loc[[309,396,485],'col2'] = [0,9,7]
df['col2'].astype('int').mean()
-0.984
  • 求col3的均值。
#df['col3'].mean() #报错
#df['col3'] #报错
df.columns
Index(['col1', 'col2', 'col3  '], dtype='object')
df.columns = df.columns.str.strip()
df.columns
Index(['col1', 'col2', 'col3'], dtype='object')
df['col3'].head()
0     363.6923
1     -152.281
2     325.6221
3    -204.9313
4         4.05
Name: col3, dtype: object
#df['col3'].mean() #报错
df['col3'][~(df['col3'].str.replace(r'-?\d+\.?\d+','True')=='True')]
28      355`.3567
37             -5
73              1
122    9056.\2253
332    3534.6554{
370             7
Name: col3, dtype: object
df.loc[[28,122,332],'col3'] = [355.3567,9056.2253, 3534.6554]
df['col3'].astype('float').mean()
24.707485

分类数据

import pandas as pd
import numpy as np
df = pd.read_csv('data/table.csv')
df.head()

`category的创建及其性质

分类变量的创建

Series创建

pd.Series(["a", "b", "c", "a"], dtype="category")
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): [a, b, c]

DataFrame指定类型创建

temp_df = pd.DataFrame({'A':pd.Series(["a", "b", "c", "a"], dtype="category"),'B':list('abcd')})
temp_df.dtypes
A    category
B      object
dtype: object

利用内置Categorical类型创建

cat = pd.Categorical(["a", "b", "c", "a"], categories=['a','b','c'])
pd.Series(cat)
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): [a, b, c]

利用cut函数创建

默认使用区间类型为标签

pd.cut(np.random.randint(0,60,5), [0,10,30,60])
[(10, 30], (0, 10], (10, 30], (30, 60], (30, 60]]
Categories (3, interval[int64]): [(0, 10] < (10, 30] < (30, 60]]

可指定字符为标签

pd.cut(np.random.randint(0,60,5), [0,10,30,60], right=False, labels=['0-10','10-30','30-60'])
[10-30, 30-60, 30-60, 10-30, 30-60]
Categories (3, object): [0-10 < 10-30 < 30-60]

分类变量的结构

一个分类变量包括三个部分,元素值(values)、分类类别(categories)、是否有序(order
从上面可以看出,使用cut函数创建的分类变量默认为有序分类变量
下面介绍如何获取或修改这些属性

describe方法

该方法描述了一个分类序列的情况,包括非缺失值个数、元素值类别数(不是分类类别数)、最多次出现的元素及其频数

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.describe()
count     4
unique    3
top       a
freq      2
dtype: object

categoriesordered属性

查看分类类别和是否排序

s.cat.categories
Index(['a', 'b', 'c', 'd'], dtype='object')
s.cat.ordered
False

类别的修改

利用set_categories修改

修改分类,但本身值不会变化

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.set_categories(['new_a','c'])
0    NaN
1    NaN
2      c
3    NaN
4    NaN
dtype: category
Categories (2, object): [new_a, c]

利用rename_categories修改

需要注意的是该方法会把值和分类同时修改

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.rename_categories(['new_%s'%i for i in s.cat.categories])
0    new_a
1    new_b
2    new_c
3    new_a
4      NaN
dtype: category
Categories (4, object): [new_a, new_b, new_c, new_d]

利用字典修改值

s.cat.rename_categories({'a':'new_a','b':'new_b'})
0    new_a
1    new_b
2        c
3    new_a
4      NaN
dtype: category
Categories (4, object): [new_a, new_b, c, d]

利用add_categories添加

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.add_categories(['e'])
0      a
1      b
2      c
3      a
4    NaN
dtype: category
Categories (5, object): [a, b, c, d, e]

利用remove_categories移除

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.remove_categories(['d'])
0      a
1      b
2      c
3      a
4    NaN
dtype: category
Categories (3, object): [a, b, c]

删除元素值未出现的分类类型

s = pd.Series(pd.Categorical(["a", "b", "c", "a",np.nan], categories=['a','b','c','d']))
s.cat.remove_unused_categories()
0      a
1      b
2      c
3      a
4    NaN
dtype: category
Categories (3, object): [a, b, c]

分类变量的排序

前面提到,分类数据类型被分为有序和无序,这非常好理解,例如分数区间的高低是有序变量,考试科目的类别一般看做无序变量

序的建立

一般来说会将一个序列转为有序变量,可以利用as_ordered方法

s = pd.Series(["a", "d", "c", "a"]).astype('category').cat.as_ordered()
s
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a < c < d]

退化为无序变量,只需要使用as_unordered

s.cat.as_unordered()
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a, c, d]

利用set_categories方法中的order参数

pd.Series(["a", "d", "c", "a"]).astype('category').cat.set_categories(['a','c','d'],ordered=True)
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a < c < d]

利用reorder_categories方法

这个方法的特点在于,新设置的分类必须与原分类为同一集合

s = pd.Series(["a", "d", "c", "a"]).astype('category')
s.cat.reorder_categories(['a','c','d'],ordered=True)
0    a
1    d
2    c
3    a
dtype: category
Categories (3, object): [a < c < d]
#s.cat.reorder_categories(['a','c'],ordered=True) #报错
#s.cat.reorder_categories(['a','c','d','e'],ordered=True) #报错

排序

先前在第1章介绍的值排序和索引排序都是适用的

s = pd.Series(np.random.choice(['perfect','good','fair','bad','awful'],50)).astype('category')
s.cat.set_categories(['perfect','good','fair','bad','awful'][::-1],ordered=True).head()
0       good
1       fair
2        bad
3    perfect
4    perfect
dtype: category
Categories (5, object): [awful < bad < fair < good < perfect]
s.sort_values(ascending=False).head()
29    perfect
17    perfect
31    perfect
3     perfect
4     perfect
dtype: category
Categories (5, object): [awful, bad, fair, good, perfect]
df_sort = pd.DataFrame({'cat':s.values,'value':np.random.randn(50)}).set_index('cat')
df_sort.head()
value
cat	
good	-1.746975
fair	0.836732
bad	0.094912
perfect	-0.724338
perfect	-1.456362
df_sort.sort_index().head()
value
cat	
awful	0.245782
awful	0.063991
awful	1.541862
awful	-0.062976
awful	0.472542

分类变量的比较操作

与标量或等长序列的比较

标量比较

s = pd.Series(["a", "d", "c", "a"]).astype('category')
s == 'a'
0     True
1    False
2    False
3     True
dtype: bool

等长序列比较

s == list('abcd')
0     True
1    False
2     True
3    False
dtype: bool

与另一分类变量的比较

等式判别(包含等号和不等号)

两个分类变量的等式判别需要满足分类完全相同

s = pd.Series(["a", "d", "c", "a"]).astype('category')
s == s
0    True
1    True
2    True
3    True
dtype: bool
s != s
0    False
1    False
2    False
3    False
dtype: bool
s_new = s.cat.set_categories(['a','d','e'])
#s == s_new #报错

不等式判别(包含>=,<=,<,>

两个分类变量的不等式判别需要满足两个条件:

  1. 分类完全相同
  2. 排序完全相同
s = pd.Series(["a", "d", "c", "a"]).astype('category')
#s >= s #报错
s = pd.Series(["a", "d", "c", "a"]).astype('category').cat.reorder_categories(['a','c','d'],ordered=True)
s >= s
0    True
1    True
2    True
3    True
dtype: bool

练习

练习一

现继续使用第四章中的地震数据集,请解决以下问题:

pd.read_csv('data/Earthquake.csv').head()
  • 现在将深度分为七个等级:[0,5,10,15,20,30,50,np.inf],请以深度等级Ⅰ,Ⅱ,Ⅲ,Ⅳ,Ⅴ,Ⅵ,Ⅶ为索引并按照由浅到深的顺序进行排序。
df_a = df.copy()
df_a['深度'] = pd.cut(df_a['深度'], [-1e-10,5,10,15,20,30,50,np.inf],labels=['Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ'])
df_a.set_index('深度').sort_index().head()


  • 在1的基础上,将烈度分为4个等级:[0,3,4,5,np.inf],依次对南部地区的深度和烈度等级建立多级索引排序。
df_a['烈度'] = pd.cut(df_a['烈度'], [-1e-10,3,4,5,np.inf],labels=['Ⅰ','Ⅱ','Ⅲ','Ⅳ'])
df_a.set_index(['深度','烈度']).sort_index().head()

时序数据

时序的创建

四类时间变量

时间点的创建

to_datetime方法

Pandas在时间点建立的输入格式规定上给了很大的自由度,下面的语句都能正确建立同一时间点

pd.to_datetime('2020.1.1')
pd.to_datetime('2020 1.1')
pd.to_datetime('2020 1 1')
pd.to_datetime('2020 1-1')
pd.to_datetime('2020-1 1')
pd.to_datetime('2020-1-1')
pd.to_datetime('2020/1/1')
pd.to_datetime('1.1.2020')
pd.to_datetime('1.1 2020')
pd.to_datetime('1 1 2020')
pd.to_datetime('1 1-2020')
pd.to_datetime('1-1 2020')
pd.to_datetime('1-1-2020')
pd.to_datetime('1/1/2020')
pd.to_datetime('20200101')
pd.to_datetime('2020.0101')
Timestamp('2020-01-01 00:00:00')

下面的语句都会报错

#pd.to_datetime('2020\\1\\1')
#pd.to_datetime('2020`1`1')
#pd.to_datetime('2020.1 1')
#pd.to_datetime('1 1.2020')

此时可利用format参数强制匹配

pd.to_datetime('2020\\1\\1',format='%Y\\%m\\%d')
pd.to_datetime('2020`1`1',format='%Y`%m`%d')
pd.to_datetime('2020.1 1',format='%Y.%m %d')
pd.to_datetime('1 1.2020',format='%d %m.%Y')
Timestamp('2020-01-01 00:00:00')

同时,使用列表可以将其转为时间点索引

pd.Series(range(2),index=pd.to_datetime(['2020/1/1','2020/1/2']))
2020-01-01    0
2020-01-02    1
dtype: int64
type(pd.to_datetime(['2020/1/1','2020/1/2']))
pandas.core.indexes.datetimes.DatetimeIndex

对于DataFrame而言,如果列已经按照时间顺序排好,则利用to_datetime可自动转换

df = pd.DataFrame({'year': [2020, 2020],'month': [1, 1], 'day': [1, 2]})
pd.to_datetime(df)
0   2020-01-01
1   2020-01-02
dtype: datetime64[ns]

时间精度与范围限制

事实上,Timestamp的精度远远不止day,可以最小到纳秒ns

pd.to_datetime('2020/1/1 00:00:00.123456789')
Timestamp('2020-01-01 00:00:00.123456789')

同时,它带来范围的代价就是只有大约584年的时间点是可用的

pd.Timestamp.min
Timestamp('1677-09-21 00:12:43.145225')
pd.Timestamp.max
Timestamp('2262-04-11 23:47:16.854775807')

date_range方法

一般来说,start/end/periods(时间点个数)/freq(间隔方法)是该方法最重要的参数,给定了其中的3个,剩下的一个就会被确定

pd.date_range(start='2020/1/1',end='2020/1/10',periods=3)
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-05 12:00:00',
               '2020-01-10 00:00:00'],
              dtype='datetime64[ns]', freq=None)
pd.date_range(start='2020/1/1',end='2020/1/10',freq='D')
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
               '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
               '2020-01-09', '2020-01-10'],
              dtype='datetime64[ns]', freq='D')
pd.date_range(start='2020/1/1',periods=3,freq='D')
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03'], dtype='datetime64[ns]', freq='D')
pd.date_range(end='2020/1/3',periods=3,freq='D')
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03'], dtype='datetime64[ns]', freq='D')

其中freq参数有许多选项:

pd.date_range(start='2020/1/1',periods=3,freq='T')
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-01 00:01:00',
               '2020-01-01 00:02:00'],
              dtype='datetime64[ns]', freq='T')
pd.date_range(start='2020/1/1',periods=3,freq='M')
DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
pd.date_range(start='2020/1/1',periods=3,freq='BYS')
DatetimeIndex(['2020-01-01', '2021-01-01', '2022-01-03'], dtype='datetime64[ns]', freq='BAS-JAN')

bdate_range是一个类似与date_range的方法,特点在于可以在自带的工作日间隔设置上,再选择weekmask参数和holidays参数
它的freq中有一个特殊的'C'/'CBM'/'CBMS'选项,表示定制,需要联合weekmask参数和holidays参数使用
例如现在需要将工作日中的周一、周二、周五3天保留,并将部分holidays剔除

weekmask = 'Mon Tue Fri'
holidays = [pd.Timestamp('2020/1/%s'%i) for i in range(7,13)]
#注意holidays
pd.bdate_range(start='2020-1-1',end='2020-1-15',freq='C',weekmask=weekmask,holidays=holidays)
DatetimeIndex(['2020-01-03', '2020-01-06', '2020-01-13', '2020-01-14'], dtype='datetime64[ns]', freq='C')

DateOffset对象

DataOffsetTimedelta的区别

Timedelta绝对时间差的特点指无论是冬令时还是夏令时,增减1day都只计算24小时
DataOffset相对时间差指,无论一天是23\24\25小时,增减1day都与当天相同的时间保持一致
例如,英国当地时间 2020年03月29日,01:00:00 时钟向前调整 1 小时 变为 2020年03月29日,02:00:00,开始夏令时

ts = pd.Timestamp('2020-3-29 01:00:00', tz='Europe/Helsinki')
ts + pd.Timedelta(days=1)
Timestamp('2020-03-30 02:00:00+0300', tz='Europe/Helsinki')
ts + pd.DateOffset(days=1)
Timestamp('2020-03-30 01:00:00+0300', tz='Europe/Helsinki')

这似乎有些令人头大,但只要把tz(time zone)去除就可以不用管它了,两者保持一致,除非要使用到时区变换

ts = pd.Timestamp('2020-3-29 01:00:00')
ts + pd.Timedelta(days=1)
Timestamp('2020-03-30 01:00:00')
ts + pd.DateOffset(days=1)
Timestamp('2020-03-30 01:00:00')

增减一段时间

DateOffset的可选参数包括years/months/weeks/days/hours/minutes/seconds

pd.Timestamp('2020-01-01') + pd.DateOffset(minutes=20) - pd.DateOffset(weeks=2)
Timestamp('2019-12-18 00:20:00')

各类常用offset对象

pd.Timestamp('2020-01-01') + pd.offsets.Week(2)
Timestamp('2020-01-15 00:00:00')
pd.Timestamp('2020-01-01') + pd.offsets.BQuarterBegin(1)
Timestamp('2020-03-02 00:00:00')

序列的offset操作

利用apply函数

pd.Series(pd.offsets.BYearBegin(3).apply(i) for i in pd.date_range('20200101',periods=3,freq='Y'))
0   2023-01-02
1   2024-01-01
2   2025-01-01
dtype: datetime64[ns]

直接使用对象加减

pd.date_range('20200101',periods=3,freq='Y') + pd.offsets.BYearBegin(3)
DatetimeIndex(['2023-01-02', '2024-01-01', '2025-01-01'], dtype='datetime64[ns]', freq='A-DEC')

定制offset,可以指定weekmaskholidays参数(思考为什么三个都是一个值)

pd.Series(pd.offsets.CDay(3,weekmask='Wed Fri',holidays='2020010').apply(i)
                                  for i in pd.date_range('20200105',periods=3,freq='D'))
0   2020-01-15
1   2020-01-15
2   2020-01-15
dtype: datetime64[ns]

时序的索引及属性

索引切片

rng = pd.date_range('2020','2021', freq='W')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
ts.head()
2020-01-05    1.513910
2020-01-12    0.921820
2020-01-19   -0.216364
2020-01-26    0.256636
2020-02-02   -1.318088
Freq: W-SUN, dtype: float64
ts['2020-01-26']
0.2566361282801419

合法字符自动转换为时间点

ts['2020-01-26':'20200726'].head()
2020-01-26    0.256636
2020-02-02   -1.318088
2020-02-09   -0.668092
2020-02-16    0.152675
2020-02-23   -1.163632
Freq: W-SUN, dtype: float64

子集索引

ts['2020-7'].head()
2020-07-05    0.756521
2020-07-12    0.170898
2020-07-19   -0.406184
2020-07-26    0.980721
Freq: W-SUN, dtype: float64

支持混合形态索引

ts['2011-1':'20200726'].head()
2020-01-05    1.513910
2020-01-12    0.921820
2020-01-19   -0.216364
2020-01-26    0.256636
2020-02-02   -1.318088
Freq: W-SUN, dtype: float64

时间点的属性

采用dt对象可以轻松获得关于时间的信息

pd.Series(ts.index).dt.week.head()
0    1
1    2
2    3
3    4
4    5
dtype: int64
pd.Series(ts.index).dt.day.head()
0     5
1    12
2    19
3    26
4     2
dtype: int64

利用strftime可重新修改时间格式

pd.Series(ts.index).dt.strftime('%Y-间隔1-%m-间隔2-%d').head()
0    2020-间隔1-01-间隔2-05
1    2020-间隔1-01-间隔2-12
2    2020-间隔1-01-间隔2-19
3    2020-间隔1-01-间隔2-26
4    2020-间隔1-02-间隔2-02
dtype: object

对于datetime对象可以直接通过属性获取信息

pd.date_range('2020','2021', freq='W').month
Int64Index([ 1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,  3,  4,  4,  4,  4,
             5,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  8,
             8,  9,  9,  9,  9, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12,
            12],
           dtype='int64')
pd.date_range('2020','2021', freq='W').weekday
Int64Index([6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
            6, 6, 6, 6, 6, 6, 6, 6],
           dtype='int64')

重采样

所谓重采样,就是指resample函数,它可以看做时序版本的groupby函数

resample对象的基本操作

采样频率一般设置为上面提到的offset字符

df_r = pd.DataFrame(np.random.randn(1000, 3),index=pd.date_range('1/1/2020', freq='S', periods=1000),
                  columns=['A', 'B', 'C'])
r = df_r.resample('3min')
r
<pandas.core.resample.DatetimeIndexResampler object at 0x7f0c0f89a7d0>
r.sum()
df_r2 = pd.DataFrame(np.random.randn(200, 3),index=pd.date_range('1/1/2020', freq='D', periods=200),
                  columns=['A', 'B', 'C'])
r = df_r2.resample('CBMS')
r.sum()

采样聚合

r = df_r.resample('3T')
r['A'].mean()
2020-01-01 00:00:00    0.050495
2020-01-01 00:03:00    0.021719
2020-01-01 00:06:00   -0.044430
2020-01-01 00:09:00   -0.015729
2020-01-01 00:12:00   -0.117970
2020-01-01 00:15:00    0.004193
Freq: 3T, Name: A, dtype: float64
r['A'].agg([np.sum, np.mean, np.std])


类似地,可以使用函数/lambda表达式

r.agg({'A': np.sum,'B': lambda x: max(x)-min(x)})

采样组的迭代

采样组的迭代和groupby迭代完全类似,对于每一个组都可以分别做相应操作

small = pd.Series(range(6),index=pd.to_datetime(['2020-01-01 00:00:00', '2020-01-01 00:30:00'
                                                 , '2020-01-01 00:31:00','2020-01-01 01:00:00'
                                                 ,'2020-01-01 03:00:00','2020-01-01 03:05:00']))
resampled = small.resample('H')
for name, group in resampled:
    print("Group: ", name)
    print("-" * 27)
    print(group, end="\n\n")
Group:  2020-01-01 00:00:00
---------------------------
2020-01-01 00:00:00    0
2020-01-01 00:30:00    1
2020-01-01 00:31:00    2
dtype: int64

Group:  2020-01-01 01:00:00
---------------------------
2020-01-01 01:00:00    3
dtype: int64

Group:  2020-01-01 02:00:00
---------------------------
Series([], dtype: int64)

Group:  2020-01-01 03:00:00
---------------------------
2020-01-01 03:00:00    4
2020-01-01 03:05:00    5
dtype: int64

窗口函数

下面主要介绍pandas中两类主要的窗口(window)函数:rolling/expanding

s = pd.Series(np.random.randn(1000),index=pd.date_range('1/1/2020', periods=1000))
s.head()
2020-01-01    0.659255
2020-01-02   -2.074411
2020-01-03   -1.354148
2020-01-04   -0.774753
2020-01-05    0.360106
Freq: D, dtype: float64

Rolling

常用聚合

所谓rolling方法,就是规定一个窗口,它和groupby对象一样,本身不会进行操作,需要配合聚合函数才能计算结果

s.rolling(window=50)
Rolling [window=50,center=False,axis=0]
s.rolling(window=50).mean()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03         NaN
2020-01-04         NaN
2020-01-05         NaN
                ...   
2022-09-22    0.044807
2022-09-23    0.071231
2022-09-24    0.098303
2022-09-25    0.110776
2022-09-26    0.085055
Freq: D, Length: 1000, dtype: float64

min_periods参数是指需要的非缺失数据点数量阀值

s.rolling(window=50,min_periods=3).mean().head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03   -0.923101
2020-01-04   -0.886014
2020-01-05   -0.636790
Freq: D, dtype: float64

count/sum/mean/median/min/max/std/var/skew/kurt/quantile/cov/corr都是常用的聚合函数

rollingapply聚合

使用apply聚合时,只需记住传入的是window大小的Series,输出的必须是标量即可,比如如下计算变异系数

s.rolling(window=50,min_periods=3).apply(lambda x:x.std()/x.mean()).head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03   -1.534925
2020-01-04   -1.308402
2020-01-05   -1.803185
Freq: D, dtype: float64

基于时间的rolling

s.rolling('15D').mean().head()
2020-01-01    0.659255
2020-01-02   -0.707578
2020-01-03   -0.923101
2020-01-04   -0.886014
2020-01-05   -0.636790
Freq: D, dtype: float64

可选closed='right'(默认)`'left'\'both'\'neither'`参数,决定端点的包含情况

s.rolling('15D', closed='right').sum().head()
2020-01-01    0.659255
2020-01-02   -1.415156
2020-01-03   -2.769304
2020-01-04   -3.544057
2020-01-05   -3.183952
Freq: D, dtype: float64

Expanding

expanding函数

普通的expanding函数等价与rolling(window=len(s),min_periods=1),是对序列的累计计算

s.rolling(window=len(s),min_periods=1).sum().head()
2020-01-01    0.659255
2020-01-02   -1.415156
2020-01-03   -2.769304
2020-01-04   -3.544057
2020-01-05   -3.183952
Freq: D, dtype: float64
s.expanding().sum().head()
2020-01-01    0.659255
2020-01-02   -1.415156
2020-01-03   -2.769304
2020-01-04   -3.544057
2020-01-05   -3.183952
Freq: D, dtype: float64

apply方法也是同样可用的

s.expanding().apply(lambda x:sum(x)).head()
2020-01-01    0.659255
2020-01-02   -1.415156
2020-01-03   -2.769304
2020-01-04   -3.544057
2020-01-05   -3.183952
Freq: D, dtype: float64

几个特别的Expanding类型函数

cumsum/cumprod/cummax/cummin都是特殊expanding累计计算方法

s.cumsum().head()
2020-01-01    0.659255
2020-01-02   -1.415156
2020-01-03   -2.769304
2020-01-04   -3.544057
2020-01-05   -3.183952
Freq: D, dtype: float64
s.cumsum().head()
Out[59]:
2020-01-01    0.659255
2020-01-02   -1.415156
2020-01-03   -2.769304
2020-01-04   -3.544057
2020-01-05   -3.183952
Freq: D, dtype: float64

shift/diff/pct_change都是涉及到了元素关系

  1. shift是指序列索引不变,但值向后移动
  2. diff是指前后元素的差,period参数表示间隔,默认为1,并且可以为负
  3. pct_change是值前后元素的变化百分比,period参数与diff类似
s.shift(2).head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03    0.659255
2020-01-04   -2.074411
2020-01-05   -1.354148
Freq: D, dtype: float64
s.diff(3).head()
Out[61]:
2020-01-01         NaN
2020-01-02         NaN
2020-01-03         NaN
2020-01-04   -1.434008
2020-01-05    2.434516
Freq: D, dtype: float64
s.pct_change(3).head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03         NaN
2020-01-04   -2.175196
2020-01-05   -1.173594
Freq: D, dtype: float64

速查

SeriesDataFrame对象的创建

Series

Seriespandas中暴露给我们使用的基本对象,它是由相同元素类型构成的一维数据结构,同时具有列表和字典的属性,字典的属性由索引赋予。

  • Series:有序,有索引
  • list: 有序,无索引
  • dict: 无序,有索引

预览

data = [1,2,3]
index = ['a','b','c']
s = pd.Series(data=data, index=index, name = 'sss')
s
a    1
b    2
c    3
Name: sss, dtype: int64
s.index  # 四个属性之一:索引
Index(['a', 'b', 'c'], dtype='object')
s.name  # 四个属性之二:名字,
'sss'
s.values # 四个属性之三:值
array([1, 2, 3], dtype=int64)
s.dtype # 四个属性之四:元素类型
dtype('int64')

创建

pd.Series(data=None, index=None, name = None)

  • data:多种类型,见下面具体介绍;
  • index:索引信息;
  • name:对data的说明,用的不多,一般在和DataFrameIndex互相转换时才需要。
data无索引

如果 datandarray(1D) 或 list(1D),那么其缺少 Series 需要的索引信息;
如果提供 index,则必须和data长度相同;
如果不提供 index,那么其将生成默认数值索引 range(0, data.shape[0])

# data = [1,2,3]
data1 = np.array([1,2,3])
index1 = ['a','b','c']
s = pd.Series(data = data1, index = index1)
s
a    1
b    2
c    3
dtype: int32
data有索引

如果 dataSeriesdict ,那么其已经提供了 Series 需要的索引信息,所以 index 项是不需要提供的;
如果额外提供了 index 项,那么其将对当前构建的Series进行 重索引(增删)(等同于reindex操作)。

# data = pd.Series([a,b,c], index = ['a','b','c'] )
data2 = { 'a':1, 'b':2,'c':3 }
index2 = ['a','b','d']
s = pd.Series(data = data2, index = index2)
s
a    1.0
b    2.0
d    NaN
dtype: float64

如上,index项用于从当前已有索引中匹配出相同的行,如果当前索引缺失给定的索引,则填充NaNNaN:not a number为pandas缺失值标记)。

DataFrame

DataFrame由具有共同索引的Series按列排列构成(2D),是使用最多的对象。

预览

data = [[1,2,3],
       [4,5,6]]
index = ['a','b']
columns = ['A','B','C']
df = pd.DataFrame(data=data, index = index, columns = columns)
df
    A	B	C
a	1	2	3
b	4	5	6
df.index  # 行索引
Index(['a', 'b'], dtype='object')
df.columns  # 列索引,由Series的name构成
Index(['A', 'B', 'C'], dtype='object')
df.values
array([[1, 2, 3],
       [4, 5, 6]], dtype=int64)
df.dtypes  # 这里的dtype带s,查看每列元素类型
A    int64
B    int64
C    int64
dtype: object

创建

pd.DataFrame(data=None, index=None, columns=None)
函数有多个参数,对我们有用的主要是:data,indexcolumns三项

data无 行索引,无 列索引

如果 datandarray(2D) or list(2D),那么其缺少 DataFrame 需要的行、列索引信息;
如果提供 indexcolumns 项,其必须和data的行 或 列长度相同;
如果不提供 indexcolumns 项,那么其将默认生成数值索引range(0, data.shape[0]))range(0, data.shape[1])

# data = [[1,2,3],
#        [4,5,6]]
data1 = np.array([[1,2,3],
                  [4,5,6]] )
index1 = ['a','b']
columns1 = ['A','B','C']
df = pd.DataFrame(data=data1, index = index1, columns = columns1)
df
    A	B	C
a	1	2	3
b	4	5	6

data无 行索引,有 列索引

如果datadict of (ndarray(1D) or list(1D)),所有ndarraylist的长度必须相同。dictkeyDataFrame提供了需要的columns信息,缺失index
如果提供 index 项,必须和list的长度相同;
如果不提供 index,那么其将默认生成数值索引range(0, data.shape[0]))
如果还额外提供了columns项,那么其将对当前构建的DataFrame进行 列重索引。

data2 = { 'A' : [1,4], 'B': [2,5], 'C':[3,6] }
index2 = ['a','b']
columns2 = ['A','B','D']
df = pd.DataFrame(data=data2, index = index2, columns = columns2)
df
    A	B	D
a	1	2	NaN
b	4	5	NaN

data有 行索引,有 列索引

如果datadict of (Series or dict),那么其已经提供了DataFrame需要的所有信息;
如果多个Seriesdict间的索引不一致,那么取并操作(pandas不会试图丢掉信息),缺失的数据填充NaN
如果提供了index项或columns项,那么其将对当前构建的DataFrame进行 重索引(reindexpandas内部调用接口)。

# data3 = { 'A' : pd.Series([1,4] ,index = ['a','b']), 'B' : pd.Series([2,5] ,index = ['a','b']), 'C' : pd.Series([3,6] ,index = ['a','c']) }
data3 = { 'A' : { 'a':1, 'b':4}, 'B': {'a':2,'b':5}, 'C':{'a':3, 'c':6} }
df = pd.DataFrame(data=data3)
df
    A	B	C
a	1.0	2.0	3.0
b	4.0	5.0	NaN
c	NaN	NaN	6.0

由文件创建

.csv文件创建

pd.read_csv(filepath_or_buffer, sep=',', header='infer', names=None,index_col=None, encoding=None )
read_csv的参数很多,但这几个参数就够我们使用了:

  • filepath_or_buffer:路径和文件名不要带中文,带中文容易报错。
  • sep: csv文件数据的分隔符,默认是',',根据实际情况修改;
  • header:如果有列名,那么这一项不用改;
  • names:如果没有列名,那么必须设置header = Nonenames为需要传入的列名列表,不设置默认生成数值索引;
  • index_collist of (int or name),传入列名的列表或者列名的位置,选取这几列作为索引;
  • encoding:根据你的文档编码来确定,如果有中文读取报错,试试encoding = 'gbk'
tips = pd.read_csv( 'tips.csv')
tips.head()
tips.index
RangeIndex(start=0, stop=244, step=1)
tips.columns
Index(['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size'], dtype='object')
tips.values
array([[16.99, 1.01, 'Female', ..., 'Sun', 'Dinner', 2],
       [10.34, 1.66, 'Male', ..., 'Sun', 'Dinner', 3],
       [21.01, 3.5, 'Male', ..., 'Sun', 'Dinner', 3],
       ...,
       [22.67, 2.0, 'Male', ..., 'Sat', 'Dinner', 2],
       [17.82, 1.75, 'Male', ..., 'Sat', 'Dinner', 2],
       [18.78, 3.0, 'Female', ..., 'Thur', 'Dinner', 2]], dtype=object)

SeriesDataFrame对象的查、改、增、 删

Series

data = [1,2,3]
index = ['a','b','c']
s = pd.Series(data=data, index=index)
s
a    1
b    2
c    3
dtype: int64
[],快捷查看
s[1] # scalar, 返回一个值
2
s[0:2] # 范围,左闭右开,返回Series切片
a    1
b    2
dtype: int64
s[[0,2]] #列表,返回Series切片
a    1
c    3
dtype: int64
mask = [False,True,False]  #mask,类似于列表,只是长度必须和Series相同,返回Series切片
s[mask]
b    2
dtype: int64
.loc[],基于索引

.loc[]查询方式和[]完全一致。

s.loc['b'] # 单索引,返回一个值
2
s.loc['a':'c'] # 范围,注意:左闭右闭,返回Series切片
a    1
b    2
c    3
dtype: int64
s.loc[['a','c']] # 列表,返回Series切片
a    1
c    3
dtype: int64
mask = [False, True, False] # mask,和iloc[]效果等同,返回Series切片
s.loc[mask]
b    2
dtype: int64
.iloc[],基于位置

无视索引,只按照位置定位。

s.iloc[1] # scalar, 返回一个值
2
s.iloc[0:2] # 范围,左闭右开,返回Series切片
a    1
b    2
dtype: int64
s.iloc[[0,2]] #列表,返回Series切片
a    1
c    3
dtype: int64
mask = [False,True,False]  #mask,类似于列表,只是长度必须和Series相同,返回Series切片
s.iloc[mask]
b    2
dtype: int64

改值

直接在查的基础上赋值,就可以修改
注意:如果要修改,使用.loc可以确保修改成功,其他方式可能会在临时创建的view上修改。

s1 = s.copy()  
s1.loc['a'] = 10   #
s1
a    10
b     2
c     3
dtype: int64
s1.loc['a':'c'] = [10,4,5]
s1
a    10
b     4
c     5
dtype: int32

函数修改:Series.replace(to_replace=None, value=None, inplace=False)

  • to_replace:要修改的值,可以为列表;
  • value:改为的值,可以为列表,与to_repalce要匹配;
  • inplace:是否在原地修改;
s1.replace(to_replace = 10, value = 100, inplace=False)
a    100
b      4
c      5
dtype: int32
改索引

直接在index上改,index类似于tuple,只能引用到别处,不能切片修改

s1 = s.copy()
s1.index = ['a','e','f']
s1
a    1
e    2
f    3
dtype: int64

函数修改:Series.rename(index=None, level = None, inplace = False)

  • indexlist or dictlist类型时必须和已有索引长度相同,dict类型可以部分修改;
  • level:多重索引时,可以指定修改哪一重,从0开始递增;
  • inplace:是否原地修改。
s1.rename(index = {'e':'b'}, inplace = False)
a    1
b    2
f    3
dtype: int64

直接增一行
s1 = s.copy()
s1.loc['d'] = 4 
s1
a    1
b    2
c    3
d    4
dtype: int64
函数增多行

Series.append(to_append, ignore_index=False, verify_integrity=False)

  • to_append: 另一个series或多个Series构成的列表;
  • ignore_indexFalse-保留原有索引,True-清除所有索引,生成默认数值索引;
  • verify_integrityTrue的情况下,如果to_append索引与当前索引有重复,则报错。
s1 = pd.Series([22,33], index = ['a', 'g'])
s.append(s1, ignore_index=False)
a     1
b     2
c     3
a    22
g    33
dtype: int64

直接删一行
#不建议使用
s1 = s.copy()
del s1['c']
s1
a    1
b    2
dtype: int64
函数删多行

Series.drop(labels, level=None, inplace=False)

  • labels:索引,单索引或索引的列表;
  • level:多重索引需要设置;
  • inplace:是否本地修改。
s1 = s.copy()
s1.drop(['a','c'])
b    2
dtype: int64

DataFrame

data = [[1,2,3],
        [4,5,6]]
index = ['a','b']
columns = ['A','B','C']
df = pd.DataFrame(data=data, index=index, columns = columns)
df
    A	B	C
a	1	2	3
b	4	5	6

[],快捷查看

[] 属于快捷查看方式,只包含下面四种,两种列操作、两种行操作。

df['A'] # 列操作,单列索引,返回Series
a    1
b    4
Name: A, dtype: int64
df[['A','C']] # 列操作,列索引列表,返回DataFrame  
    A	C
a	1	3
b	4	6
# df[0]  # 报错,0不是列名
df[0:1]  # 行操作,位置范围,返回DataFrame
    A	B	C
a	1	2	3
# df[[0,1]] #报错,不能这样使用
mask = [False,True] 
df[mask] # 行操作,mask,必须和行长度一致,返回DataFrame
    A	B	C
b	4	5	6
.loc[],基于索引

.loc[]DataFrame中与[]不一致。
DataFrame 有两维,每一维都和 Series.loc[] 用法相同;
Series有四种方式,所以DataFrame有16种方式;
可以缺省后面维度,默认补全为 ':'
下面都以第一维度为例,第二维可以类比。

df.loc['b','B'] # 返回单一值,因为两维都是单索引
5
df.loc['a':'b', 'B'] #返回Series,如果只有一维是单索引
a    2
b    5
Name: B, dtype: int64
df.loc[['a','b'],'B'] #返回Series,如果只有一维是单索引
a    2
b    5
Name: B, dtype: int64
mask1 = [False, True, False] 
df.loc[mask1,'B']
b    5
Name: B, dtype: int64
.iloc[],基于位置

无视索引,只按照位置定位。

DataFrame 有两维,每一维都和 Series.iloc[] 用法相同;
Series有四种方式,所以DataFrame有16种方式;
可以缺省后面维度,默认补全为 ':'
下面都以第一维度为例,第二维可以类比。

df.iloc[1,1] # 返回单一值,因为两维都是scalar
5
df.iloc[0:2,0]  # 返回Series,如果只有一维是scalar
a    1
b    4
Name: A, dtype: int64
df.iloc[[0,1],[0,2]] # 返回DataFrame
    A	C
a	1	3
b	4	6
mask1 = [False, True, False]  # 返回DataFrame
mask2 =[True,False]
df.iloc[mask1,mask2]
    A
b	4

改值

在查的基础上赋值进行修改,.loc[]方法确保在原地修改。

#修改单值
df1 = df.copy()
df1.loc['a','A'] = 10
df1
    A	B	C
a	10	2	3
b	4	5	6
#修改单列
df1.loc[:, 'A'] = [100,200]  
df1
    A	B	C
a	100	2	3
b	200	5	6
#修改多列
df1.loc[:, ['A','B']] = [[1,2],[3,4]]  
df1
    A	B	C
a	1	2	3
b	3	4	6
#修改行,见下面的增减操作

函数批量任意修改:
DataFrame.replace(to_replace=None, value=None, inplace=False)

  • to_replace:要修改的值,可以为列表;
  • value:改为的值,可以为列表,与to_repalce要匹配;
  • inplace:是否在原地修改;
df1.replace(to_replace = 10, value = 100, inplace=False)
A	B	C
a	1	2	3
b	3	4	6
改索引

直接在索引上改,索引类似于tuple,必须全改,不能切片修改

df1 = df.copy()
df1.index = ['e','f']
df1.columns = ['E','F','G']
df1
    E	F	G
e	1	2	3
f	4	5	6

函数修改:
DataFrame.rename(index=None, columns = None, level = None, inplace = False)

  • indexlist or dictlist类型时必须长度相同,dict类型时可以部分修改;
  • columnslist or dictlist时必须长度相同,dict时可以部分修改;
  • level:多重索引时,可以指定修改哪一重,目前还用不着;
  • inplace:是否原地修改。
df1.rename( index = {'e':'b'},columns = {'E':'A'}, inplace = False)
    A	F	G
b	1	2	3
f	4	5	6

直接增一行
df1 = df.copy()
df1.loc['c'] = [7,8,9]
df1
    A	B	C
a	1	2	3
b	4	5	6
c	7	8	9
函数增多行

pd.concat(objs, axis=0)
确保 列索引 相同,行增加。 (其实这个函数并不要求列索引相同,它可以选择出相同的列。而我写这个教程遵循了python的宣言—明确:做好一件事有一种最好的方法,精确控制每一步,可以少犯错。)

  • objs: list of DataFrame
  • axis: 取0,行增加操作。
df1 = pd.DataFrame([[22,33,44],[55,66,77]], index = ['c','d'],columns = ['A','B','C'])
pd.concat([df,df1], axis = 0 )
    A	B	C
a	1	2	3
b	4	5	6
c	22	33	44
d	55	66	77
直接增一列
df1 = df.copy()
df1['H'] = [7,8]
df1
    A	B	C	H
a	1	2	3	7
b	4	5	6	8
函数增多列

pd.concat(objs, axis=1)
确保行索引相同,列增加

  • objs: list of DataFrame
  • axis: 取1,列增加操作。
df1 = pd.DataFrame([[22,33],[44,55]], index = ['a','b'],columns = ['D','E'])
pd.concat([df,df1], axis =1)
    A	B	C	D	E
a	1	2	3	22	33
b	4	5	6	44	55

函数删多行

DataFrame.drop(labels, axis = 0, level=None, inplace=False)

  • labels:索引,单索引或索引的列表;
  • axis0,删行;
  • level:多重索引需要指定;
  • inplace:是否本地修改。
df1 = df.copy()
df1.drop(['a'],axis =0 )
    A	B	C
b	4	5	6
直接删一列
#不建议使用
df1 = df.copy()
del df1['A']
df1
B	C
a	2	3
b	5	6
函数删多列

DataFrame.drop(labels, axis = 1, level=None, inplace=False)

  • labels:索引,单索引或索引的列表;
  • axis:1,删列;
  • level:多重索引需要指定;
  • inplace:是否本地修改。
df1 = df.copy()
df1.drop(['A','C'],axis =1 )
    B
a	2
b	5

merge详解

函数说明
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort = False)
concat函数本质上是在所有索引上同时进行对齐合并,而如果想在任意列上对齐合并,则需要merge函数,其在sql应用很多。

  • left,right: 两个要对齐合并的DataFrame
  • how: 先做笛卡尔积操作,然后按照要求,保留需要的,缺失的数据填充NaN
    • left: 以左DataFrame为基准,即左侧DataFrame的数据全部保留(不代表完全一致、可能会存在复制),保持原序;
    • right: 以右DataFrame为基准,保持原序;
    • inner: 交,保留左右DataFrameon上完全一致的行,保持左DataFrame顺序;
    • outer: 并,按照字典顺序重新排序;
  • on:对应列名或者行索引的名字,如果要在DataFrame相同的列索引做对齐,用这个参数;
  • left_on, right_on, left_index, right_index
    • on对应列名或者行索引的名字(所以行索引一般要跟列一样看待,有自己的名字),用这俩参数;DataFrame中用作连接键的列名;这个参数中左右列名不相同,但代表的含义相同时非常有用。
    • index对应要使用的index,不建议使用,会搞晕。使用DataFrame中的行索引做为连接键,用到这个参数时,就有点类似于JOIN函数了
  • sort: True or False,是否按字典序重新排序。
df1 = pd.DataFrame([[1,2],[3,4]], index = ['a','b'],columns = ['A','B'])
df2 = pd.DataFrame([[1,3],[4,8]], index = ['b','d'],columns = ['B','C'])
df1
    A	B
a	1	2
b	3	4
df2
    B	C
b	1	3
d	4	8

如果单纯的按照index对齐,不如用concat方法。

pd.merge(left = df1, right = df2, how = 'inner' ,left_index = True, right_index = True)
    A	B_x	B_y	C
b	3	4	1	3
#小区别是concat对重复列没有重命名,但是重名的情况不多,而且重名了说明之前设计就不大合理。
pd.concat([df1,df2], join = 'inner',axis =1)
    A	B	B	C
b	3	4	1	3

on 用法

设置 how = 'inner'

#对于'B'列:df1的'b'行、df2的'd'行,是相同的,其他都不同。 
pd.merge(left = df1, right = df2, how = 'inner' , on =['B'])
    A	B	C
0	3	4	8
# df1的'A'列'b'行,df2的'C'列'd'行是相同的,其他都不同。
# 其他列如果同名会进行重命名。
pd.merge(left = df1, right = df2, how = 'inner',left_on = ['A'] ,right_on = ['C'])
    A	B_x	B_y	C
0	3	4	1	3

how 用法

#保持左侧DataFrame不变,用右侧来跟它对齐,对不上的填NaN。
pd.merge(left = df1, right = df2, how = 'left', on = ['B'] )
    A	B	C
0	1	2	NaN
1	3	4	8.0
# 保持右侧DataFrame不变,用右侧来跟它对齐,对不上的填NaN。
pd.merge(left = df1, right = df2, how = 'right', on = ['B'] )
A	B	C
0	3.0	4	8
1	NaN	1	3

对齐的列存在重复值
重复的也没关系,操作逻辑是一致的,完全可以假想不存在重复。

df1.loc['a','B'] =4  #改成重复
df1
    A	B
a	1	4
b	3	4
### 保持右侧的列都在,如果左侧对齐的列存在重复值,那么对齐上后也存在重复。
pd.merge(left = df1, right = df2, how = 'right', on = ['B'] )
    A	B	C
0	1.0	4	8
1	3.0	4	8
2	NaN	1	3

Index对象的创建,查、改、增、删和使用

  • 索引类似于元组,其本身是不能赋值修改的;
  • 其在数据进行整体运算时,辅助自动对齐,这是pandas不同于其他数据处理库的一大特征;
  • 多层索引可以帮助改变表的形态,如透视表等。

单层索引

创建

pd.Index(data, dtype = Object, name = None)

  • name:一维列表
  • dtype:索引元素的类型,默认为object
  • name:索引的名字,类似于列的名字
data = ['a','b','c']
index = pd.Index(data, name = 'name1')
index
Index(['a', 'b', 'c'], dtype='object', name='name1')

从返回值可以看到,index由三部分组成,可以分别查看。

index.name
'name1'
index.values
array(['a', 'b', 'c'], dtype=object)
index.dtype
dtype('O')

查询方式和一维ndarraySeries.iloc[]完全一样。

index[0] # scalar,返回值
'a'
index[0:2] # 范围,返回index
Index(['a', 'b'], dtype='object', name='name1')
index[[0,2]] # 列表,返回index
Index(['a', 'c'], dtype='object', name='name1')
mask = [True,False,True]  # mask,返回index
index[mask]
Index(['a', 'c'], dtype='object', name='name1')

改索引名

虽然索引的值是不能修改的,但是名字确是可以修改的。

直接改
index.name = 'new_name' 
index
Index(['a', 'b', 'c'], dtype='object', name='new_name')
函数改

Index.set_names(names, inplace=False)

  • names:要设置的名字,可以为名字的列表;
  • inplace:是否原地修改。
index.set_names('new_name')
Index(['a', 'b', 'c'], dtype='object', name='new_name')

按位置添加一行

Index.insert(loc, value)

  • loc:位置编号
  • value:值
index
Index(['a', 'b', 'c'], dtype='object', name='new_name')
index.insert(1,'d')
Index(['a', 'd', 'b', 'c'], dtype='object', name='new_name')
尾部添加多行

Index.append(other)

  • other:其他索引对象
index1 = index.copy()
index1
Index(['a', 'b', 'c'], dtype='object', name='new_name')
index1.append( index)
Index(['a', 'b', 'c', 'a', 'b', 'c'], dtype='object', name='new_name')

Index.union(other)

index2 = pd.Index(['b','c','d'])
index2
Index(['b', 'c', 'd'], dtype='object')
index1.union(index2)
Index(['a', 'b', 'c', 'd'], dtype='object')

按位置删除一行

Index.delete(loc)

  • loc:位置编号
index1.delete(1)
Index(['a', 'c'], dtype='object', name='new_name')

Index.intersection(other)

index1.intersection(index2) 
Index(['b', 'c'], dtype='object')

多层索引

创建

pd.MultiIndex.from_tuples(labels, names = None)

  • labels:元组或列表的列表;
  • names:名字的列表。
# data = [['a','one'],['a','two'],['b','one']]
data = [('a','one'),('a','two'),('b','one')]
index = pd.MultiIndex.from_tuples( data, names = ['name1','name2'])
index
MultiIndex(levels=[['a', 'b'], ['one', 'two']],
           labels=[[0, 0, 1], [0, 1, 0]],
           names=['name1', 'name2'])
s = pd.Series([1,2,3], index = index)
s
name1  name2
a      one      1
       two      2
b      one      3
dtype: int64

查询方法和单层索引完全一致。

index[0]  # scalar,返回值
('a', 'one')
index[0:2]  # 范围,返回MultiIndex
MultiIndex(levels=[['a', 'b'], ['one', 'two']],
           labels=[[0, 0], [0, 1]],
           names=['name1', 'name2'])
index[[0,2]] # 列表,返回MultiIndex
MultiIndex(levels=[['a', 'b'], ['one', 'two']],
           labels=[[0, 1], [0, 0]],
           names=['name1', 'name2'])
mask = [True,False, True] # mask,返回MultiIndex
index[mask]
MultiIndex(levels=[['a', 'b'], ['one', 'two']],
           labels=[[0, 1], [0, 0]],
           names=['name1', 'name2'])

获取某一层索引:
MultiIndex.get_level_values(level)

  • levelint,选中的那一层
index.get_level_values(0)
Index(['a', 'a', 'b'], dtype='object', name='name1')
index.get_level_values(1)
Index(['one', 'two', 'one'], dtype='object', name='name2')

改索引名(函数改)

MultiIndex.set_names(names, level=None, inplace=False)

  • names:要设置的名字,可以为名字的列表;
  • level:多层索引需要设置修改的索引层次,可以为列表,要与names匹配;
  • inplace:是否原地修改。
index.set_names('new_name_1',level=0)
MultiIndex(levels=[['a', 'b'], ['one', 'two']],
           labels=[[0, 0, 1], [0, 1, 0]],
           names=['new_name_1', 'name2'])
改索引层次顺序

MultiIndex.swaplevel(i=-2, j=-1)
改变`level i 和level j的次序

index.swaplevel()
MultiIndex(levels=[['one', 'two'], ['a', 'b']],
           labels=[[0, 1, 0], [0, 0, 1]],
           names=['name2', 'name1'])

Series.swaplevel(i=-2, j=-1)
DataFrame.swaplevel(i=-2, j=-1, axis = 1)

  • axis:0-行索引,1-列索引。 这两个函数更实用一些。
s.swaplevel()
name2  name1
one    a        1
two    a        2
one    b        3
dtype: int64
columns = index.copy()
columns.set_names( names = ['name3','name4'], level = [0,1], inplace = True) #列索引取和行索引相同,只是改了名字
df = pd.DataFrame([[1,2,3],[4,5,6],[7,8,9]], index= index, columns = columns)
df
df.swaplevel( axis =1)  # 交换列索引顺序

多层索引使用方法

values进行查看时,多层索引可以分开使用。

df1 = df.copy()
df1


索引为空不代表缺失,缺省写法,意思是之前的索引一致。

对于外层索引

记住:无论是Series还是DataFrame,外层索引都是可以直接使用,也就是说可以认为只有这一层索引

[]
df1['b']  # 列外层
df1[['a','b']] # 列外层
df1[0:2]  # 行外层
mask =[True,False,True]  # 行外层
df1[mask]
.loc[]
df1.loc['a','b']  # 单行索引'a' 
df1.loc['a':'b','b']   #范围
df1.loc[['a','b'],'b'] #列表
mask = [True, False, True] # mask
df1.loc[mask,'b']
.iloc[]
df1.iloc[0,0:2] 
name3  name4
a      one      1
       two      2
Name: (a, one), dtype: int64
df1.iloc[0:2,0:2] 
df1.iloc[[0,1],0:2] 
mask = [True, False, True]
df1.iloc[mask,0:2]

对于内层索引

内层索引不可直接使用,必须先外层、再内层,直接使用会报错;
内层只能使用单索引形式,其他形式报错。

[ , ]
df1
df1['a','one']  #取一列,先外层单列索引,再内层单列索引,其他形式都报错
name1  name2
a      one      1
       two      4
b      one      7
Name: (a, one), dtype: int64
.loc[ , ]
df1.loc['a','one'] # 取一行,先外层单行索引,再内层单列索引,其他形式都报错
name3  name4
a      one      1
       two      2
b      one      3
Name: (a, one), dtype: int64

xs直接选取法

适合在单层level选取,不能行列同时操作。
Series.xs(key, level=None, drop_level=True)
DataFrame.xs(key, axis=0, level=None, drop_level=True)

  • key: 要选取的索引值或其列表;
  • axis:0-行索引,1-列索引;
  • level:索引层次;
  • drop_levelTrue or False,是否显示用于选取的level索引,默认不显示。
df1 = df.copy()
df1
df1.xs( 'one', axis = 0, level = 1 )  # 行索引的level 1, 有两行
df1.xs( 'two', axis = 1, level = 1 ) # 列索引的level 1,有一列

普通列和行index的相互转化

data = [[1,2,3],[4,5,6]]
index = ['a','b']
columns = ['A','B','C']
df = pd.DataFrame( data = data, index = index, columns = columns)
df
	A	B	C
a	1	2	3
b	4	5	6

普通列 转化为 行索引

DataFrame.set_index(keys, drop=True, append=False, inplace=False)
-keys:用于转化的列名称,列名或列名的列表,如果是列表则转化为多重索引;

  • dropTrue or False,是否保留原列;
  • appendTrue or False,是否保留当前索引;
  • inplaceTrue or False,是否原地修改。

DataFrame中的列

注意:下面的这种用法只能处理单层列索引情况。

df.set_index('A', drop = True, append = False) #单列转化为行索引,列名作为索引的名字
    B	C
A		
1	2	3
4	5	6
df.set_index(['A','B'], drop = True, append = False) # 列名的列表
        C
A	B	
1	2	3
4	5	6
df.set_index(['A','C'], drop = False, append = False) # drop = False, 保留原列
        A	B	C
A	C			
1	3	1	2	3
4	6	4	5	6
df.set_index(['A','C'], drop = False, append = True) # append = True, 保留原索引
            A	B	C
    A	C			
a	1	3	1	2	3
b	4	6	4	5	6

任意列

普通列不是必须为DataFrame中的列,任何行数匹配的列表或列表的元组都可以;
此方法可以处理多层列索引情况,方法是先提取需要的列,构建成元组,再来替换索引。

li = ['c','d']
df.set_index(keys = [li], append = False)# 注意是[li]不是li,如果是单层,会当做列名处理
## append为False,实现了改索引功能
    A	B	C
c	1	2	3
d	4	5	6
#如果是多层索引情况,append为False,所有索性会被替换
df1 = df.set_index(keys = [li], append = True)
df1
        A	B	C
a	c	1	2	3
b	d	4	5	6
df1.set_index(keys = [['e','f']],append = False)
    A  	B	C
e	1	2	3
f	4	5	6
# 元祖的列表建立多层索引
list_tuple = [('e','f'),('g','h')]
df1.set_index(keys = [list_tuple],append = False)
        A	B	C
e	f	1	2	3
g	h	4	5	6

行索引 转化为 普通列

DataFrame.reset_index(level=None, drop=False, inplace=False, col_level=0, col_fill='')

  • levelint 或者行索引的名字,可以为列表,默认包含所有行索引。
  • dropTrue or False,是否将行索引插入到列中,默认是插入;
  • inplace:是否本地修改
  • col_level:如果列索引也是多重的,那么新插入的列设置哪一重索引;
  • col_fill:如果有多重索引,除了col_level已经设置的

单层 列索引 情况

df1 = df.set_index(['A','C'], drop = True, append = True)
df1
            B
    A	C	
a	1	3	2
b	4	6	5
# df1.reset_index(level = 'A', drop = False)
df1.reset_index(level = 1, drop = False) #单 行索引
        A	B
    C		
a	3	1	2
b	6	4	5
df1.reset_index(level = [1,2], drop = False) # 行索引的列表
    A	C	B
a	1	3	2
b	4	6	5
In [13]:
#如果所有行索引都恢复为列,那么将生成默认数值索引,如果行索引没有名字,那么生成默认的名字
df1.reset_index(level = [0,1,2], drop = False)
level_0	A	C	B
0	a	1	3	2
1	b	4	6	5
df1.index.set_names(names = 'ab', level = 0, inplace = True)
df1
            B
ab	A	C	
a	1	3	2
b	4	6	5
#行索引如果有名字,那么将其设置为列名
df1.reset_index(level = [0,1,2], drop = False)
    ab	A	C	B
0	a	1	3	2
1	b	4	6	5
df1.reset_index(level = [1,2], drop = True) # drop = True, 直接丢掉选中的行索引,不插入
    B
ab	
a	2
b	5

多层 列索引 情况

df1 = df.copy()
df1.columns = pd.MultiIndex.from_tuples( [('one','A'),('one','B'),('two','C')])
df1
     one   two
    A	B	C
a	1	2	3
b	4	5	6
#多重索引下,如果行索引没有名字,那么变为列之后在列索引的外层生成了默认索引:index。
df1.reset_index(level = 0)
index	 one   two
        A	B	C
0	a	1	2	3
1	b	4	5	6
#多重索引下,如果行索引有名字,那么变为列之后在列索引的外层索引名作为列名。
df1.index.set_names(['ab'], inplace = True)
df1
     one   two
    A	B	C
ab			
a	1	2	3
b	4	5	6
df1.reset_index(level = 0)
    ab	 one   two
        A	B	C
0	a	1	2	3
1	b	4	5	6