这篇主要介绍一些我觉得常用的一些高级用法,主要包括groupby操作,apply,map操作,pivot_table操作,时间序列操作和字符串操作。
groupby
核心:
- 不论分组键是数组、列表、字典、Series、函数,只要其与待分组变量的轴长度一致都可以传入groupby进行分组。
- 默认axis=0按行分组,可指定axis=1对列分组。
对数据进行分组操作的过程可以概括为:split-apply-combine三步:
- 按照键值(key)或者分组变量将数据分组。
- 对于每组应用我们的函数,这一步非常灵活,可以是python自带函数,可以是我们自己编写的函数。
- 将函数计算后的结果聚合。
如下图:
#导入pandas库
import pandas as pd
#生成一个dataframe
data = pd.DataFrame({'key':['A','B','C','A','B','C','A','B','C'],
'value':[0,5,10,5,10,15,10,15,20]})
print(data)
print(data.groupby('key').sum())
key value
0 A 0
1 B 5
2 C 10
3 A 5
4 B 10
5 C 15
6 A 10
7 B 15
8 C 20
value
key
A 15
B 30
C 45
上面只是进行了sum操作,当然也可以使用许多的统计方法size,count,max,min,mean等等。
需要注意的地方就是size方法和count方法的区别,size是直接统计键的出现次数,所以结果里面是不会有value列的,而且size方法会统计缺失值,count不会。
import numpy as np
#导入numpy,设置一个值为nan值
data2 = pd.DataFrame({'key':['A','B','C','A','B'],'value':[0,5,10,5,np.nan]})
print(data2)
print(data2.groupby('key').size())
print(data2.groupby('key').count())
key value
0 A 0.0
1 B 5.0
2 C 10.0
3 A 5.0
4 B NaN
key
A 2
B 2
C 1
dtype: int64
value
key
A 2
B 1
C 1
#注意看结果中的细节
此外可以将group单独拿出来做一些操作。
#先分组,但是没有进行聚合操作,得到的是一个groupby对象
groups = data.groupby('key')
print(groups)
#可以仿照字典格式来遍历
for index,group in groups:
print('---------------')
print(index,'\n',group)
print('-------------')
#当然也可以在遍历时加入一些特殊操作。
print(group.mean())
---------------
A
key value
0 A 0
3 A 5
6 A 10
-------------
value 5.0
dtype: float64
---------------
B
key value
1 B 5
4 B 10
7 B 15
-------------
value 10.0
dtype: float64
---------------
C
key value
2 C 10
5 C 15
8 C 20
-------------
value 15.0
dtype: float64
get_group()
使用这个方法可以选取想要的group,按键值索引
data3 = pd.DataFrame({'key':['A','B','A','B'],'value':[0,5,10,5]})
print(data3)
print(data3.groupby('key').get_group('A'))
print(data3.groupby('key').get_group('B'))
key value
0 A 0
1 B 5
2 A 10
3 B 5
key value
0 A 0
2 A 10
key value
1 B 5
3 B 5
多重索引
很多情况下我们都会遇到多重索引的问题,如何更好的去使用groupby呢,可以制定level参数
level = 0表示只使用第一重索引,level = 1表示只使用第二重索引,依次类推,也可以直接指定索引名称。
#这里先使用pandas的方法生成一个二级索引结构的series
arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
index = pd.MultiIndex.from_arrays(arrays,names = ['first','second'])
s = pd.Series(np.random.randn(8),index = index)
print(s)
print('------------------')
print(s.groupby(level =0).sum())
print('------------------')
print(s.groupby(level =1).sum())
print('------------------')
print(s.groupby(level ='first').sum())
first second
bar one -2.102766
two 0.967173
baz one 0.177320
two -1.231985
foo one -0.192680
two 0.793084
qux one 1.140791
two -0.045113
dtype: float64
------------------
first
bar -1.135594
baz -1.054665
foo 0.600404
qux 1.095679
dtype: float64
------------------
second
one -0.977335
two 0.483159
dtype: float64
------------------
first
bar -1.135594
baz -1.054665
foo 0.600404
qux 1.095679
dtype: float64
我这里只列举了很小的一部分操作,有时间要上官网仔细看看
groupby官网
自定义函数
pandas中可以使用apply,map,agg进行自定义函数操作,apply和agg操作的一行,map操作的是具体的一个值,但是apply()会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起,所以效率是最高的。注意groupby中最好就使用apply聚合,因为map不能,agg我测试时只用上了封装好的方法。
#自定义一个函数,作用是是将值加1
data3 = pd.DataFrame({'key':['A','B','A','B'],'value':[0,5,10,5]})
print(data3)
def add_1(value):
return value + 1
print("value直接使用 add函数")
print(data3['value'].apply(add_1))
print(data3['value'].map(add_1))
print(data3['value'].agg(add_1))
print('groupby后使用 add函数聚合')
print(data3.groupby('key').apply(add_1))
print(data3.groupby('key').agg({'value':np.mean}))
key value
0 A 0
1 B 5
2 A 10
3 B 5
value直接使用 add函数
0 1
1 6
2 11
3 6
Name: value, dtype: int64
0 1
1 6
2 11
3 6
Name: value, dtype: int64
0 1
1 6
2 11
3 6
Name: value, dtype: int64
groupby后使用 add函数聚合
value
0 1
1 6
2 11
3 6
value
key
A 5
B 5
熟练使用apply和map可以省去大量代码篇幅,同时效率也高,我列举一个我打天池O2O比赛的代码列子。
在这里我使用map方法和lambda函数处理就很方便的得到了结果
#将折扣卷转化为折扣率
off_train['discount_rate'] = off_train['Discount_rate'].map(lambda x : float(x) if ':' not in str(x) else
(float(x.split(':')[0]) - float(x.split(':')[1])) / float(x.split(':')[0]))
off_train['discount_rate'].head()
数据透视表
数据透视表(Pivot Table)是一种交互式的表,可以进行某些计算,如求和与计数等。所进行的计算与数据跟数据透视表中的排列有关。
之所以称为数据透视表,是因为可以动态地改变它们的版面布置,以便按照不同方式分析数据,也可以重新安排行号、列标和页字段。每一次改变版面布置时,数据透视表会立即按照新的布置重新计算数据。另外,如果原始数据发生更改,则可以更新数据透视表。
pandas使用pivot_table()方法生成数据透视表。
pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All', observed=False)
data即为要操作的dataframe,values为需要进行操作的列,index为行索引,columns为列索引,aggfunc为进行聚合的操作,默认为均值,fill_value默认不填充缺失值,可设置填充,margins设为true的话会添加所有的行和列,这里的dropna是删除全为nan的列。
一个简单的求和例子:
#生成一个dataframe
df = pd.DataFrame({"A": ["foo", "foo", "foo", "foo", "foo",
"bar", "bar", "bar", "bar"],
"B": ["one", "one", "one", "two", "two",
"one", "one", "two", "two"],
"C": ["small", "large", "large", "small",
"small", "large", "small", "small",
"large"],
"D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
"E": [2, 4, 5, 5, 6, 6, 8, 9, 9]})
print(df)
print('--------------------------')
#将A,B作为行索引,C作为列索引,对D进行求和操作
print(pd.pivot_table(df, values='D', index=['A', 'B'],
columns=['C'], aggfunc=np.sum))
A B C D E
0 foo one small 1 2
1 foo one large 2 4
2 foo one large 2 5
3 foo two small 3 5
4 foo two small 3 6
5 bar one large 4 6
6 bar one small 5 8
7 bar two small 6 9
8 bar two large 7 9
--------------------------
C large small
A B
bar one 4.0 5.0
two 7.0 6.0
foo one 4.0 1.0
two NaN 6.0
填充缺失值为0:
print(pd.pivot_table(df, values='D', index=['A', 'B'],
columns=['C'], aggfunc=np.sum, fill_value=0))
C large small
A B
bar one 4 5
two 7 6
foo one 4 1
two 0 6
在多个列中取多种聚合情况:
print(pd.pivot_table(df, values=['D', 'E'], index=['A', 'C'],
aggfunc={'D': np.mean,
'E': [min, max, np.mean]}))
D E
mean max mean min
A C
bar large 5.500000 9 7.500000 6
small 5.500000 9 8.500000 8
foo large 2.000000 5 4.500000 4
small 2.333333 6 4.333333 2
更多方法详见pivot_table官网
时间序列操作
时间戳
#生成时间戳
import pandas as pd
ts = pd.Timestamp('2019-8-19')
print(ts)
2019-08-19 00:00:00
时间序列
pandas.date_range(start=None, end=None, periods=None, freq=None, tz=None, normalize=False, name=None, closed=None, **kwargs)[source]
#生成时间序列
#生成时间序列,periods表示往后10次,freq默认为一天,设置为12H表示没12H一次
print(pd.Series(pd.date_range(start='2019-8-19',periods = 10,freq = '12H')))
0 2019-08-09 00:00:00
1 2019-08-09 12:00:00
2 2019-08-10 00:00:00
3 2019-08-10 12:00:00
4 2019-08-11 00:00:00
5 2019-08-11 12:00:00
6 2019-08-12 00:00:00
7 2019-08-12 12:00:00
8 2019-08-13 00:00:00
9 2019-08-13 12:00:00
dtype: datetime64[ns]
转换为时间格式
pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, box=True, format=None, exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
这里对参数不做解释,有兴趣去官网查看to_datetime官网
#转换为时间格式
s = pd.Series(['2017-11-24 00:00:00','2017-11-25 00:00:00','2017-11-26 00:00:00'])
print(s)
ts = pd.to_datetime(s)
print(ts)
0 2017-11-24 00:00:00
1 2017-11-25 00:00:00
2 2017-11-26 00:00:00
dtype: object
0 2017-11-24
1 2017-11-25
2 2017-11-26
dtype: datetime64[ns]
下面我加入数据集讲时间序列的操作
数据集:
链接: https://pan.baidu.com/s/1X9jvXoxlaLDNqww4sa8P1A 密码: e20i
#读入数据集
data = pd.read_csv('./data/flowdata.csv')
print(data.head())
Time L06_347 LS06_347 LS06_348
0 2009-01-01 00:00:00 0.137417 0.097500 0.016833
1 2009-01-01 03:00:00 0.131250 0.088833 0.016417
2 2009-01-01 06:00:00 0.113500 0.091250 0.016750
3 2009-01-01 09:00:00 0.135750 0.091500 0.016250
4 2009-01-01 12:00:00 0.140917 0.096167 0.017000
可以看到这个数据的第一列是时间
我们将其转换为时间格式并将它当成索引
#将Time列转换为时间格式
data['Time'] = pd.to_datetime(data['Time'])
#设置Time列为索引
data = data.set_index('Time')
print(data.head())
L06_347 LS06_347 LS06_348
Time
2009-01-01 00:00:00 0.137417 0.097500 0.016833
2009-01-01 03:00:00 0.131250 0.088833 0.016417
2009-01-01 06:00:00 0.113500 0.091250 0.016750
2009-01-01 09:00:00 0.135750 0.091500 0.016250
2009-01-01 12:00:00 0.140917 0.096167 0.017000
上述方法太麻烦了,我们可以在读入数据时就对其进行操作,使用index_col和parse_dates参数
data = pd.read_csv('./data/flowdata.csv',index_col = 'Time',parse_dates = True)
print(data.head())
L06_347 LS06_347 LS06_348
Time
2009-01-01 00:00:00 0.137417 0.097500 0.016833
2009-01-01 03:00:00 0.131250 0.088833 0.016417
2009-01-01 06:00:00 0.113500 0.091250 0.016750
2009-01-01 09:00:00 0.135750 0.091500 0.016250
2009-01-01 12:00:00 0.140917 0.096167 0.017000
使用时间索引
#时间切片
print(data[('2012-01-01 09:00'):('2012-01-01 19:00')])
#直接索引年份
print(data['2013'])
#索引2012年1到2月的数据
print(data['2012-01':'2012-02'].head(10))
L06_347 LS06_347 LS06_348
Time
2012-01-01 09:00:00 0.330750 0.293583 0.029750
2012-01-01 12:00:00 0.295000 0.285167 0.031750
2012-01-01 15:00:00 0.301417 0.287750 0.031417
2012-01-01 18:00:00 0.322083 0.304167 0.038083
L06_347 LS06_347 LS06_348
Time
2013-01-01 00:00:00 1.688333 1.688333 0.207333
2013-01-01 03:00:00 2.693333 2.693333 0.201500
2013-01-01 06:00:00 2.220833 2.220833 0.166917
2013-01-01 09:00:00 2.055000 2.055000 0.175667
2013-01-01 12:00:00 1.710000 1.710000 0.129583
2013-01-01 15:00:00 1.420000 1.420000 0.096333
2013-01-01 18:00:00 1.178583 1.178583 0.083083
2013-01-01 21:00:00 0.898250 0.898250 0.077167
2013-01-02 00:00:00 0.860000 0.860000 0.075000
L06_347 LS06_347 LS06_348
Time
2012-01-01 00:00:00 0.307167 0.273917 0.028000
2012-01-01 03:00:00 0.302917 0.270833 0.030583
2012-01-01 06:00:00 0.331500 0.284750 0.030917
2012-01-01 09:00:00 0.330750 0.293583 0.029750
2012-01-01 12:00:00 0.295000 0.285167 0.031750
2012-01-01 15:00:00 0.301417 0.287750 0.031417
2012-01-01 18:00:00 0.322083 0.304167 0.038083
2012-01-01 21:00:00 0.355417 0.346500 0.080917
2012-01-02 00:00:00 1.069333 0.970000 0.071917
2012-01-02 03:00:00 0.886667 0.817417 0.070833
此外还可以使用bool类型索引
#索引所有1月的数据
print(data[data.index.month == 1].head())
#索引8点到12点的数据
print(data[(data.index.hour > 8) & (data.index.hour <12)].head())
#上一个用法也可以直接使用between_time方法
print(data.between_time('08:00','12:00').head())
L06_347 LS06_347 LS06_348
Time
2009-01-01 00:00:00 0.137417 0.097500 0.016833
2009-01-01 03:00:00 0.131250 0.088833 0.016417
2009-01-01 06:00:00 0.113500 0.091250 0.016750
2009-01-01 09:00:00 0.135750 0.091500 0.016250
2009-01-01 12:00:00 0.140917 0.096167 0.017000
L06_347 LS06_347 LS06_348
Time
2009-01-01 09:00:00 0.135750 0.091500 0.016250
2009-01-02 09:00:00 0.141917 0.097083 0.016417
2009-01-03 09:00:00 0.124583 0.084417 0.015833
2009-01-04 09:00:00 0.109000 0.105167 0.018000
2009-01-05 09:00:00 0.161500 0.114583 0.021583
L06_347 LS06_347 LS06_348
Time
2009-01-01 09:00:00 0.135750 0.091500 0.016250
2009-01-01 12:00:00 0.140917 0.096167 0.017000
2009-01-02 09:00:00 0.141917 0.097083 0.016417
2009-01-02 12:00:00 0.147833 0.101917 0.016417
2009-01-03 09:00:00 0.124583 0.084417 0.015833
时间序列重采样
resample方法类似于groupby,只是这个方法是针对时间进行聚合运算
DataFrame.resample(self, rule, how=None, axis=0, fill_method=None, closed=None, label=None, convention='start', kind=None, loffset=None, limit=None, base=0, on=None, level=None)
官网
#按天求均值
print(data.resample('D').mean().head())
#按年求和
print(data.resample('Y').sum().head())
L06_347 LS06_347 LS06_348
Time
2009-01-01 0.125010 0.092281 0.016635
2009-01-02 0.124146 0.095781 0.016406
2009-01-03 0.113562 0.085542 0.016094
2009-01-04 0.140198 0.102708 0.017323
2009-01-05 0.128812 0.104490 0.018167
L06_347 LS06_347 LS06_348
Time
2009-12-31 640.507333 639.476750 71.634750
2010-12-31 994.468583 1029.896542 75.620100
2011-12-31 704.901583 692.756488 49.946350
2012-12-31 669.086250 659.646667 64.412572
2013-12-31 14.724333 14.724333 1.212583
字符串操作
pandas.Series.str中封装了许多处理字符串类型列的方法,合理运用的话能达到很好的效果。
one hot 独热编码,get_dummies
series=data['列名'].str.get_dummies(sep=',')
s = pd.Series(['a','a|b','a|c'])
print(s.str.get_dummies(sep = '|'))
a b c
0 1 0 0
1 1 1 0
2 1 0 1
切分字符串,split()
series=data['列名'].str.split(',')
把DataFrame列中字符串以','分隔开,每个元素分开后存入一个列表里
series=data['列名'].str.split(',',expand=True)
参数expand,这个参数取True时,会把切割出来的内容当做一列,产生多列。
s = pd.Series(['a_b_C','c_d_e','f_g_h'])
print(s.str.split('_'))
print(s.str.split('_',expand = True))
0 [a, b, C]
1 [c, d, e]
2 [f, g, h]
dtype: object
0 1 2
0 a b C
1 c d e
2 f g h
3.替换,replace()
series=data['列名'].str.replace(',','-')
#替换列名
df = pd.DataFrame(np.random.randn(3,2),columns = ['A a','B b'],index = range(3))
df.columns = df.columns.str.replace(' ','_')
print(df)
A_a B_b
0 -0.634455 -0.255455
1 -1.189211 0.659666
2 -0.310230 0.200333
4.是否包含表达式,contains()
series=data['列名'].str.contains('we')
返回的是布尔值series
s = pd.Series(['A','Aas','Afgew','Ager','Agre','Ager'])
print(s.str.contains('Ag'))
0 False
1 False
2 False
3 True
4 True
5 True
dtype: bool
5.查找所有符合正则表达式的字符findall()
series=data['列名'].str.findall("[a-z]")
以数组的形式返回
s = pd.Series(['A','Aas','Afgew','Ager','Agre','Ager'])
print(s.str.findall('Ag'))
0 []
1 []
2 []
3 [Ag]
4 [Ag]
5 [Ag]
dtype: object
此外还有以下方法等,就不一样列举了。
len(),计算字符串的长度
series=data['列名'].str.len()
strip(),去除前后的空白字符
series=data['列名'].str.strip()
rstrip() 去除后面的空白字符
lstrip() 去除前面的空白字符
isalnum() 是否全部是数字和字母组成
isalpha() 是否全部是字母
isdigit() 是否全部都是数字
isspace() 是否空格
islower() 是否全部小写
isupper() 是否全部大写
istitle() 是否只有首字母为大写,其他字母为小写
这一篇介绍了一些pandas的高级用法,下一篇讲讲运用pandas内置函数画图的实例。