《pandas官方文档》读书笔记
2021-04-24 21:01:55 53 举报
AI智能生成
《pandas官方文档》是一份详尽的指南,旨在帮助用户理解和使用Python编程语言中的pandas库。该文档提供了丰富的教程、示例和解释,涵盖了pandas库的各种功能和用法。通过学习这份文档,用户可以掌握如何创建和操作数据表、进行数据清洗和转换、执行统计分析以及绘制图表等技能。此外,文档还介绍了pandas与其他Python库的集成方式,如NumPy、Matplotlib和Seaborn等。无论是初学者还是有一定经验的开发者,都能从《pandas官方文档》中获得宝贵的知识和指导,提升自己在数据处理和分析领域的能力。
作者其他创作
大纲/内容
基础的数据结构
Series
DataFrame
读取和保存DataFrame
csv的读取和保存
read_csv
文件读取
分隔符的处理
sep = ' ,'
csv 默认逗号(,)为分隔符,
但也有特殊
‘\t’
由制表符为分隔符的tsv文件。
‘\s’
由空格为分割符的csv文件。
注意:
使用 sep = '\s+'
来指定空格作为分隔符。单纯\s可能无法正确解析数据。
也可以将 delim_whitespace = True
效果和上面一样。
delimiter = ','
如果指定的delimiter的话,sep会失效。
comments = “#”
指定注释文档,#后的不会被解析。
相同的还有
quoting
单引号
doublequote
双引号
这个用到的不多
行处理
header = 0
如果csv文件中有列,则可以通过header 指定哪一行为标题行。
如果没有,则可以通过指定 names = names 来确定标题行。
具体的放在列的处理
skiprows = [1,2,3]
跳过那些行,如果是整数,前几行。
如果是列表,则是指定行
也可以是函数,比如,跳过所有的奇数行。
自定义函数传递的值是行的索引值。
函数返回值为False 的行,保留
函数返回为True时,行 跳过
一个例子:只读取前10行
子主题
nrows = 10
读取多少行数据。
skip_blank_lines = True
跳过所有的空行。
列处理
names = names
如果没有标题行,则指定一个名称列表,作为列名。
prefix = ‘col_’
不设置names也可以,可以设置前缀名,pandas会自动给列加前缀名。
index_col = 1
index_col = [1,2]
指定某一列或者某几列,作为索引列。
usecolos = (1,2,3)
读取特定的列,
也可以通过列名 usecols = ['name','id','age’]
mangel_dupe_cols
这个没啥用。
数据的预处理
converters = {1:conver}
定义一个数据处理函数,对数据进行预处理。
根据timestamp转换为时间 lambda t : time.strftime("%Y-%m-%d",time.gmtime(t))
dtype = {1:np.int}
自定义每一列的数据类型。
NaN的处理
na_values = ['','nan', 'NaN']
其实,pandas内置了一个 na_values列表,当keep_dafault_na = True时,会调用这个列表。
当读取的数据中存在na_values定义的字段时,就被转换为NaN类型数据。通过NaN索引,可以处理这些空值
keep_default_na = True
接上,默认为True,基本上读取csv文件时,除非特别指定,直接用默认参数就可以了。na_values也可以不用设置。
na_filter = True
空值检查开关,默认是打开的,如果读取大数据,部分空值对整体结果没影响时,可以设置为False,可以提高读取速度。
verbose = False
如果设置为True,会返回每一行空值的个数。
时间字符串的处理
parse_dates
parse_dates = [[0,1,2]]
自动将第1,2,3列合并,并生成一个时间序列。
原来的三列被删除,合并为一个列,列名为三个列名相连接。
parse_dates = {'time':['year', 'month', 'day']}
新创建一个新列,将年月日三列,合并生成一个时间序列。
原来三列,还在,生成了一个新列。
合并会调用pandas默认的parse函数,但是如果遇到两位数年的时候,函数解析的可能不准确,可能出现20或者19
所以需要自定义一个链接函数:date_parser = func
另外连个选项
parse_dates = [0,1,2]
parse_dates = False
目前还没搞清楚。
date_parser
date_parser = time_conver
自定义解析函数,覆盖默认的解析函数
infer_datetime_format = False
当为True时,pandas会内部解析,速度会比自定义的date_parser函数快一些。
数字的格式化
thousands = ‘,’
千位逗号显示
decimal = '.'
小数点显示
float_percision
浮点数保留几位有效数字
其他
encoding = 'utf-8'
编码
error_bad_lines = True
当设置为False时,遇到错误行,会跳过。
engine = {'c','python'}
解析引擎,最好设置为python 自由度高些。
to_csv
原始dataframe
子主题
保存文件名
sep = ','
分割符
na_rep = ''
将NaN转换为 ‘’空
columns = ['ROWNUM_','XMMC','XMJC','GLCSNAME','BTJE','FFRQ'],
保存到csv的有哪些列?
子主题
根据DataFrame可以自己定义每列的排序。而不是默认排序。
header = ['序号','项目名称','项目简称','相关处室','补贴金额','发放时间'],
给列名重新起一个名字。
注意:列名和columns,是一一对应的。
encoding = 'utf-8'
编码:默认为utf-8
mode = 'w'
写模式,同文件处理的mode
a w r + 等。
关于索引
index
如果为False,写入scv文件时,不会新增一列index,如果是True,写入csv文件时,会新增一列为index
index_label
给新增的列起名,如果为False,不起名
如果为None, 起一个空名
如果为str,起一个实名
如果是一个序列,则会创建一个复合索引。
使用复合索引需要注意,读取csv 的时候会出现数据不对齐的情况,尽量不要用。
JSON的读取和保存
read_json
orient :可以解析的几种json数据结构。
split
records
index
columns
values
table
typ:读取为Series 还是DataFrame
默认为 'frame'
'series'
dtype:定义每一列的数据类型。
None
True
系统判断
{'FFSJ',datetime}
encoding:编码类型
‘utf-8’
关于编码的问题,网络json文件,大部分都是"\u4f4e",需要注意读取时指定相应的编码,否则会出现无法解析的。ValueError
read_json的编码不是很稳定,如果从网上读取json文件,先用json库,loads方法,转换为字典、列表类型。然后再DataFrame读取
这样可以减少不可预知的编码错误
也可以有更大的处理数据转换的自由度
自动转换数据类型
convert_axes
尝试根据每个column内容,转换数据格式
默认为:True
convert_dates
选取特定列转换为日期。
这个功能有些鸡肋,且没有自定义转换函数的选项,datetime.strptime
keep_default_dates
自动解析,可能的时间列
默认为True,同样鸡肋,只有特定格式才能解析。
date_unit
这个函数少用!
to_json
重点关注两个参数
储存的路径
orient:编码成特定的json数据结构
split
records
index
columns
values
table
格式处理的参数
double_precision:小数保留几位小数
默认 10
时间数据的处理
date_format
epoch
时间戳
ISO
date_unit:时间单位
ns
ms
us
default_handler
如果一个对象没有转换成一个恰当的JSON格式,处理程序就会被调用。采用单个参数,即要转换的对象,并返回一个序列化的对象。
df中的NaN和NaT 转换成json 会被是识别为null
读取json文件时,会被解析为NaN
excel的读取和保存
read_excel
文件名或文件对象
sheet_name
'sheet1'
读取指定的工作表名
['sheet1','sheet2']
读取多个工作表
生成含有多个dataframe的字典
如果所有表的结构类似,无需数据清洗,可以使用,否则不建议使用
0
读取第几个工作表
默认读取第一个工作表
[0,1,'sheet5']
None
读取所有工作表
行处理
skiprows = 1
从第开始几行开始读取数据。
skipfooters = 4
从底部数第几行不读取。
nrows = 10
一共读取10行数据
同上。
列处理
headers = 1
指定第几行作为列名。
注意和skiprows的关系,如果指定了headers,默认从headers指定的行读取数据,skiprows可以不用设置。
如果想保留原来excel表的标题,headers = [0,1]
但是此时会变成复合索引,要使用复合索引的方式查找数据。
names = ['序号','姓名','性别','身份证号','家庭人口','与户主关系','民族','脱贫状态','贫困户性质','联系电话'],
usecols可以使用Excel 的列名
如果数据中没有标题,或者标题不规范,可以自定义一个标题。
usecols = [1,2]
['A','C:K']
'A,B,C,D:K'
字符串也行
['姓名','身份证号']
列名也行
index_col = 1
选择第几列为索引
[0,1]
如果是序列,会生成复合索引
数据处理
converters = {1:func1, '姓名':func2}
自定义数据处理函数
空值的处理
na_values
那些情况可以认定为空值NaN
['','NULL','NAN','null','nan']
如果出现了这个列表中的情况,就被认定为NaN
keep_default_na
时间数据的处理
parse_dates
date_parser
自定义时间处理函数。功能和read_csv类似
dtype = {1:np.str}
定义每一列的数据类型。
comment = "#"
所有# 标记,都不会读取
to_excel
保存文件的路径
columns
可以自定义列的顺序,以及保留那些列。
header
True
用原来 dateframe的列名
list
自己定义列名
index
True
自动生成索引列
False
不自动生成索引列,此时可以指定相应的列作为索引。
index_label
指定列作为索引
list
涉及到复合索引
startrow
从第几行写入数据
startcol
从第几列写入数据
其他参数
特殊数据处理
na_rep
空值的描述.默认 ‘’
inf_rep
inf数据的描述
熏染引擎
engine
openpyxl
XlsxWriter
如何自定义to_excel的格式可以参考
encoding
如果用xlwt 渲染Excel 需要指定 utf-8,其他不需要指定。
html的读取和保存
索引和数据查询
loc和iloc
数据
loc和iloc是pandas最常用的索引函数。loc是基于label,iloc基于位置,索引方式与numpy类似。
loc的用法
调用方式df.loc['行索引','列索引']
返回第一行的数据
df.loc[1]
返回第一列的数据
df.loc[:,'项目名称']
返回第一行到第十行的数据
df.loc[1:10]
注意:loc的索引是前后都包含的,df.loc[0,2] --> 0,1,2
numpy的索引依然遵循的是左开右闭。n[0:2] --> 0,1
返回前10行,‘项目名称’和‘补贴金额’
df.loc[1:10,['项目名称','补贴金额']]
注意:如果列索引,不是切片,需要用[]或(),将需要索引的列括起来。
返回前10行,前3列的数据
df.loc[1:10,'项目名称':'相关处室']
注意:这个数据中,自定义了索引列,序号,所以序号列是不可以用来索引的。
df.loc[1:10,'序号':'相关处室']
会抛出keyerror的异常。
注意:这里的行索引,是自定义的,所以行索引并非位置参数,而是具体索引的值。如果需要复合索引,方式如下
df.loc[('idx_1','idx_2')]
和numpy一样,loc也支持布尔索引。
df.loc[df.发放时间 > datetime(2019,1,1)]
df.loc[df.发放时间 > datetime(2019,1,1),['项目名称','补贴金额']]
也支持函数调用。
df.loc[lambda df:df['补贴金额'] > 1000]
df.loc[:,lambda df:df.columns == '项目名称']
查找最近60天的记录
通过证件号码查找年龄段的信息
注意:多个逻辑运算,要用逻辑运算符 &(and) |(or) ^(not)
其次,涉及到series和dateframe的元素级运算,根据数据类型,调用对应的操作符
df2['证件号码'].str.slice(6,10)
通过str的切片功能,可以获取出生年。
df2.loc[df2['姓名'].str.startswith('刘')]
寻找所有“刘”姓的信息。
df['发放日期'].dt.year == 2017
查找2017年的记录
注意:loc索引的结果是数据的视图,而不是副本,所以直接赋值会改变原来数据的值。
iloc的用法
iloc的索引是基于位置进行索引。
支持
整数索引
df.iloc[0]
获取第一行数据
返回的是series
可以调用series.to_list()方法,返回数据列表。
整数数组索引
df.iloc[[0,3,4,9,10]]
获取多行数据
注意,这里传入的是一个列表
df.iloc[1,2,3,4]
df.iloc[[1,2,3,4]]
行和列的索引
df.iloc[:5,1:3]
获取前5行,第2和第3列的数据。
切片
df.iloc[1:10]
注意:iloc是基于位置,所以遵循的是左开右闭的规则!
df.iloc[1:10] -->1-9
df.loc[1:10] --> 1-10
loc中的[1:10]代表的是索引值。而不是位置。
如果索引是字符串或者是datetime,[1:10] 就会报错!
获取第1到第9位数据
支持布尔索引
注意!
如果通过df['人数'] < 3 获得的是布尔类型的series,并不是布尔数组
和loc不同,iloc的布尔索引基于数组并非series,所以按照以上的索引,会抛出异常
NotImplementedError: iLocation based boolean indexing on an integer type is not available
如果你非要用,将df['人数'] < 3 获得的布尔类型的series转换成布尔数组即可。
mask = df['人数'] < 3
mask = mask.to_numpy(dtype=np.bool)
列表也可以:mask = mask.to_list()
支持函数调用索引
注意!
尽量少用
原因同上!
[]
dataframe['列名']
df['col_1']
返回某一列的值
df[['col_1','col_2']]
返回某几列的值
df的赋值运算
df2[['脱贫属性','贫困户属性']] = df2[['贫困户属性','脱贫属性']]
可以实现两列数据互换。
也可以通过列的运算生成新列
df2['年龄'] = 2019 - find_age(df2['证件号码'])
注意:涉及到多列赋值时,如果使用loc/iloc时,运算顺序是先对齐后运算。
[] 的切片操作
df[1:10]
同df.iloc[1:10]
df[10:1:-1]
反转结果
df[1:10,2:4]
不支持多维度的索引。原因,[] 是调用了df中的__getitem__方法。并不是调用类似numpy 的索引方法。
series['label']
[]也支持布尔索引的操作。
.
df.列名
s.index
注意,这种用法少用,除非是index是自己制定的非数字、且与多数函数名不冲突去的前提下。
其他方法
sample() 随机选取数据
df.sample()
s.sample()
和np.random.choice()
功能一样
主要参数
n
从数据中随机挑选n个。
frac
比例:从样本数据中抽取的比例
从样本中抽取2%的数据
注意:n和frac 二选一
replace
是否允许重复取样
True
如果frac大于1,replace必须为True
False
不允许重复取样
weights
权重
isin() is in 是否在某个序列中,在返回True,不在返回False
isin()
series.isin()
index.isin()
dataframe.isin()
返回的是布尔类型的series。
生成mask,df[df['相关处室'].isin(['处室1','处室2'])]
isin 也可以操作Index
df.index.isin([1,2,3])
这个功能对于索引符合索引非常有用
df.index.isin([('a','a_1'),('b','b_1')])
isin可以操作series也可以操作dateframe
series的isin() 针对的是一列series的值。
dataframe的isin() 针对的所有值。
返回的是和dataframe一样结构的布尔DataFrame
where() 查找所有数据,符合条件的显示,不符合条件显示NAN或者指定字符。
where()
index
series
DataFrame
主要参数
cond : 条件,df['发放金额']>1000
结果
子主题
如果是series
s.where(s>1000)
结果
other
将所有不符合条件的结果,替换成other的值
other
series 序列
dataframe
callable 函数
series 序列
inplace
是否改变原数据
默认为False
如果为True
将直接在原数据上修改,不会生成副本。
axis
对应的轴,思考numpy的广播原则
没有定义axis,
例如:df.where(df>0,-df) 这样不会报错,
但是 df.where(df>0,df['a'])则会报错。
ValueError: Must specify axis=0 or 1
mask()
和where功能相反,遮住符合条件的数据!
query() 通过查询字符串查询
类似于python的eval()函数
pandas.eval()
eval的作用:把字符串转换成代码。
支持数学运算符,也能支持链式比较,
df.eval('a < 10')
df.eval('a>b>c')
mask = df3.eval('aa > bb and ee > ff')
可以提高查询的运行效率
基于numexp引擎和pandas引擎。
df3.query('(a>b)&(h>j)')
query的用法
简单查询
df.query('人数 > 2')
df[df['人数'] > 2]
df.loc[df['人数'] > 2]
mask = df.loc[:,'人数'] > 2
df[mask]
df[mask]
带有参数的查询,要用""
df2.query('性别 == "男"')
df[df['性别'] == '男']
in 和 not in 以及 ==/!=
df2.query('贫困户属性 == ["低保户","低保贫困户"]')
等价于
df2.query('贫困户属性 in ["低保户","低保贫困户"]')
mask = df2["贫困户属性"].isin(["低保户","低保贫困户"])
df2[mask]
df2[mask]
df2.query('贫困户属性 not in ["低保户"]')
& ~ |
例子
查询家庭人口大于3,贫困户属性为低保贫困户的数据
df2.query('人数 > 3 and 贫困户属性 == "低保贫困户"')
df2.query('人数 > 3 & 贫困户属性 == "低保贫困户"')
数学运算符
例子
查询符合构成三角形的数组
df3.query(' a + b > c')
多种条件的组合
query和eval组合应用
在一组随机数据中寻找符合三角形的数据,并计算面积
去除重复数据
duplicated()
标记所有有重复的数据。
duplicated()的逻辑
将所有出现了重复的数据标记为True,
根据keep的参数确定,在这些重复数据中,那些可以免于标记
first
保留第一条数据,其他全部标记
last
保留最后一条数据,其他全部标记
False
只要出现重复都进行标记。
duplicated() 的应用场景
主要用于dataframe的数据查重。
df.duplicated()
使用。df.drop_duplicates()
将完全重复的数据删除。
series的查找重复要慎重,但是取反的话,会返回和unique()一样的结果。
drop_duplicates()
删除被标记的重复元素。
返回的是被去除重复后的数据。
注意inplace
如果为true 的话,会直接在原来的Dataframe中删除
如果为False的话,为直接返回一个删除重复后的副本。
get()方法
lookup()方法
复合索引(MultiIndex)和高级索引
创建复合索引的实例方法
复合索引的本质:原本的单一数据索引,变成了多个数据构成的索引元组。
索引后的效果
通过列表创建
from_arrays()
pd.MultiIndex.from_array([['A','A','A','B','B','B','C','C',],['R1','R2','R3','R1','R2','R3','R1','R2']])
也可以更多的数组
例子
表结构
使用array需要注意的是,列表的长度必须是一样的,否则无法解析。
如果自定义array的长度,也需要注意,这个长度和series或者dataframe的数据长度是一样的。
例子
通过元祖创建
from_tuples()
通过多个可迭代对象创建
from_product()
显示的结构
通过DataFrame实例创建
from_frame()
根据dataframe生成复合索引
例子
通过在创建DataFrame时,根据数据长度,传入一个数组列表自动创建复合索引
例子
注意:可以传入[np.array,np.array],
但是不支持传入np.array[list1,list2,list3]
这样的numpy数组。会抛出不支持的异常。
但是不支持传入np.array[list1,list2,list3]
这样的numpy数组。会抛出不支持的异常。
unsupported format string passed to numpy.ndarray.__format__
如何通过复合索引定位数据
通过loc进行索引
注意:单一索引,一个索引的值可以是一个数字或者字符串。复合索引,单个索引值是一个元组。所以在进行复合索引的时候,索引index,要使用元组(1961,1,1)。使用[1961,1,1]会引来歧义,不要使用。
df.loc[(2016,10,1)]
返回符合索引2016,10,1行的所有数据
df.loc[(2016,10,1):(2016,10,10)]
返回2016,10,1行到2016,10,10行的数据。
df.loc[(2016,10,1),['col_1','col_2']]
返回2016,10,1这一行,第一列和第二列的数据。
wind_df.loc[[(1961,1,2),(1961,1,10)]]
返回1961,1,2和1962,1,10这两行。
wind_df.loc[([1961,1962],1,[2,10])]
返回 1961,1962 1月 2,10这两天数据。体会和上面的差异。
df.loc的切片操作slice
df.loc[(slice(None),1,slice(1,7)),:]
注意:使用slice方法时,一定要弄清slice 是哪个轴【行或者是列】否则会抛出异常。
wind_df.loc[(slice(1961,1962),1,slice(1,10)),:]
指明了对index进行切片。
wind_df.loc[(slice(1961,1962),1,slice(1,10))]
没有指明,系统会不清楚,到底对哪个进行切片。并抛出。too many indexers 的异常。
虽然,使用wind_df.loc[(1961,1,slice(1,10))]不会报错,但是尽量不要这么做。
slice(None)
和numpy不同,进行切片索引时,复合索引不能直接用 :
wind_df.loc[(:,1,[1,2,3]),:],这种写法是是错误的!
SyntaxError: invalid syntax
补充一:如果想使用:,则需要IndexSlice 类创建一个索引切片的实例。
idx = pd.IndexSlice
wind_df.loc[idx[:,1,[1,2,3]],:]
使用布尔索引
mask = (wind_df[0] > wind_df[1]) & (wind_df[0] > 30)
使用先mask后loc的方法
wind_df[mask].loc[(1962,slice(None),[0,1])]
直接用通过idx,直接使用mask的方法
wind_df.loc[idx[1962,:,maks],[0,1]]
这种方式的运行速率更高。
补充二:也可以指定轴,使用 :
wind_df.loc(axis=0)[1962,:,:3]
也可以使用mask
wind_df.loc(axis=0)[1962,:,mask]
注意:当指定了索引轴后,只能索引index,不能索引columns
xs()
根据MultiIndex的值进行索引
wind.xs(1961,level='year')
wind.xs((1961,12),level=('year','month'),drop_level=False)
drop_level
False
不去除索引的项
True
去除索引的项
(1961,12,1)
对应的level('year','month','day')
take() 通过位置进行索引
wind.take([0,1])
索引第1,2行
wind.take([0,1],axis=1)
索引第1,2列
注意:不支持切片索引和布尔索引,虽然布尔索引不会报错,但是结果很诡异。
复合索引的其他设置
通过level对复合索引进行计算
例
level的转换问题
wind.swaplevel(0,1,axis=0)
将两个指定的轴对调
wind.swaplevel(1,2,axis=0)
将第1轴和第二轴对调。
wind.reorder_levels(['day','month','year'],axis=0)
对复合索引的level进行重新排序
对索引进行重命名
wind.rename()
关于rename需要注意的几点
图示:
几个例子:
wind.rename(columns={'city_1':'城市1'})
将city_1的列名修改为“城市_1”
wind.rename(columns=lambda city:"城市"+ "_"+ city.split('_')[-1])
将所有的城市名称改成中文。
wind.rename(index=lambda year:year - 1900,level='year')
将index中关于年的显示有19XX 变为 XX
wind.rename_axis()
注意:不是对应轴的内容,而是轴本身。
如:wind.rename_axis('city',axis=1)
给列加了一个名字city
columns增加了一个名字。
rename_axis在修改复合索引和重新定义索引的应用场景中比较常用。
比如,将复合索引中的 year.month.day改为年月日。
wind.rename_axis(index=['年','月','日'])
查看修改的结果。
关于index的排序问题
sort_index()
wind.sort_index(level='year',ascending=False,axis=0)
根据年降序排列。
图
如果想精确控制每个level的升降序:
wind.sort_index(level=['year','month'],ascending=[False,True],axis=0)
年为降序,月为升序
拼接和关联查询
拼接
pd.concat((df1,df2))
作用:拼接多个dataframe 、series
参数
obj
带拼接的dataframe列表
pd.concat((df1,df2))
axis 拼接的轴
0 行拼接
1 列拼接
keys:将拼接结果的index变成multiindex,通过keys指定最外层level的值
没有key
设置了keys pd.concat((df1,df2),keys=['df1','df2])
可以通过 dfc.loc['df1'] 迅速定位需要的数据。
注意:如果拼接的是series,且series定义了name属性,那么拼接的部分index会使用name属性
例子
s1 = pd.Series(np.random.randint(1,10,5)) 没有定义name属性,新增的列名为0
s1 = pd.Series(np.random.randint(1,10,5),name='I'),定义了name属性,新增的列名为I
如果拼接的都是series,则可以通过指定key,定义新增列的名称,如果是dataframe 和 series拼接,则会生成multiindex这里需要注意。
series的拼接只在列方向上
如果想在行上面拼接series,可以用df的append方法。并且series的index,同df的columns要一致才能对齐。
注意2:如果不想指定key,在传入df列表时,使用字典,也可以实现key的效果
ignore_index:忽略index
True:拼接后会自动生成一个新的默认序列
False:拼接后,依然用原来的Index,但是容易出现index重复。
names:如果使用了keys,或者拼接的df都有复合索引时,可以指定复合索引每个level的名字。
levels:暂时不知道作用
verify_integerity:对于重复index、columns 的处理
True
出现相同的Index,将会报错。当然如果设置了ignore_index=True就不会报错。
False
忽略
join
如果出现不对齐的情况该如何处理。
‘inner':只保留对齐的部分,不对齐的部分删除
outer:不对齐的部分也保留,但是不对齐的地方会显示NaN
例子
df1
子主题
df2
子主题
无论是行,还是列这两个df无法完全对齐。
行对齐的情况。
子主题
列对齐的情况
子主题
如果选择“inner”不对齐的数据将被删除
列对齐
子主题
sort:不用管,当使用join,出现不对齐时,会有警告,此时任意指定sort=True或者sort=False,忽略警告。
df1.append(df2)
注意:
1、append是concat的一种快捷操作方法,只在行的方向上添加数据
2、与list的append不同,df1.append(df2)会生成一个新的df而不是修改df1
3、与concat不同的是,append没有join参数,根据列对齐后,不对齐的部分会显示NaN。
图例
主要参数
df1.append(df2)
新增一个df
df1.append(df2)
注意的columns是否对齐,不对齐的话,不对齐的部分会显示NaN
新增一个series
df1.append(s1)
注意series的index是否和df1的columns对齐,不对齐的话,会根据series的index新增列,而并非在原来df的行上面新增一行。
series的index和df1的columns 对齐的情况
没有指定series的index情况
如果series没有定义name属性,且ignore_index为False的话,会抛出异常,建议将ignore_index设置为True!
ignore_index
df1.append(df2,ignore_index=True)
True重新定义index
False 使用原来的index
verify_integrity
验证index的单一性,如果df2的index与df1的index有重复,会抛出异常。
sort
不用管,新版本会移除
pd.merge(df1,df2) 关联合并
简介:
主要参数
left 和 right
连接查询的两个表。
on
关联的列或者索引的名称
可以是一列,也可以是多列
一列
pd.merge(left,right,on='key')
多列
pd.merge(left,right,on=['key1','key2'])
how
关联方式
left
没有匹配的情况下,左边的表信息会保留
例子:
数据:
pd.merge(a1,a2,on='id',how='left')
结果:a1的信息都会被保留,a2没有匹配项,会显示NaN
子主题
right
没有匹配的情况下,右边的表信息会保留
例子
inner
没有匹配的信息,都不会保留
例子
子主题
outer
没有匹配的信息,都会保留
例子
子主题
比较
子主题
left_on/right_on 和 right_index/left_index
几种情况
如果两张表没有关联字段时,使用index进行关联。
例子:原始数据
两张表,没有有效的关联字段,但index 可以。
md = pd.merge(a1,a2,right_index=True,left_index=True)
结果
how='inner' 默认
how='right'
how='outer'
另外一个例子
原始数据
子主题
pd.merge(a1,a2,right_index=True,right_index=True)
how='inner'
how='left'
how='right'
how='outer'
一张表用关联字段【id】,另一外一张表用index
例子:原始数据
a1表没有与a2表关联的字段时,可以使用将a1【左表】的index作为关联字段。left_index=True,将这个关联字段同a2的id字段进行关联。right_on='id'
pd.merge(a1,a2,left_index=True,right_on='id')
结果
默认连接方式 how='inner'
how='left',左表优先,左表的信息会被保留,
关联结果的index也会使用左表的index。
如果左表的index右表的id字段未匹配,
结果表的index会显示NaN
关联结果的index也会使用左表的index。
如果左表的index右表的id字段未匹配,
结果表的index会显示NaN
how='right',右表优先,会保留右表的信息。
how='outer'两边信息都保留
如果两个表中任意一个表,有复合索引的情况
例子
原始数据
如果用pd.merge(left,right,left_index=True,right_on='id')会抛出异常。
解决方法:pd.merge(left,right,left_on='id',right_on='id')
两张表没有同名字段
原始数据:
有这种情况
也会有这种情况
pd.merge(a1,a2,left_on='ID',right_on='id')
第一种情况的结果,如果一个表的index同另一个表的column进行关联
第二种情况的结果,两边字段都会保留。
这种情况的衍生问题。最有意思和价值的结果!!!
如图当两张表存在逻辑上关联的index,但是名称不同的情况
直接用pe.merge(a1,a2,left_on='ID',right_on='id')会报错!
pd.merge(a1,a2,left_on='ID',right_index=True)
结果:
suffixes
如果两个表中有同名的列,为了便于区分,可以为它们指定前缀
如:md2 = pd.merge(df1,df3,left_index=True,right_index=True,suffixes=('_df1','_df2'))
图示
如果是用concat,只会根据指定的轴,对齐两张表。如果是根据行对齐,同名的列名,不会重命名。在这一点上,merge要好一些。
图示
copy
True 默认
生成的关联的结果,是两个表的副本,无论这两个表的后来会发生怎样的变化,并不会影响本次关联查询的结果。
如果为False,生成关联查询的结果,是两个表的视图,如果两个表发生变化,这个结果也会变化。
从性能和占用的磁盘空间上来说,False会比较好,但是前提是在处理数据时,要关注后续操作会不会对原始表进行操作,如果有操作,则需要慎重,防止出现意想不到的数据结果。
validate
关联关系
"1:1"
两边的key是一一对应。单对单
例子,一对一关系,会检查两个表的唯一性,如果两个表任意一个表中的关联的key重复都会抛出异常。
当两个有重复的key时,抛出异常。
"1:m"
一对多
左边是单一的。
"m:1"
多对一
右边的key是唯一的。
"m:m"
多对多
两边key都不是唯一
多对多的关系,在数据库操作中多对多关系很常见,但是在数据处理中,尽量少用多对多关系。
indicator
提示匹配关系
如果为True
在最后增加名为_merge的列,并提示关联关系。
both 代表两边匹配
left_only
左边有数据,右边没有匹配的数据
right_only
右边有数据,左边没有匹配的数据
也可以给这个merge列重命名
indicator = '匹配情况'
关于匹配后的数据类型问题。
如果数据完全匹配,数据类型会保持不变
但是如果遇到了NaN,纯数据列会变成float, 所以注意一下。
一种特殊的数据类型 CategoricalDtype
后面再关注。
df1.join(df2)
简介:
主要参数
on
左表与右表【index】关联的字段
这里需要注意的是,join与merge不同的地方在于,join的连接是基于index的,最快捷的是两个表的index拥有一样的数据逻辑。
注意容易出现的陷阱
尝试连接这两个表
子主题
如果用:left.join(right,on='id') 并不会得到左右两个表关联字段id的结果!
而是left表的id字段,与右表的index【0,1,2,3,4】进行关联的结果
逻辑上的结果应该为:
所以一定要注意!!如果想要得到期望的结果,一定要理解join方法的适用场景,与右表的index进行关联!
解决方法
left.join(right.set_index('id'),on='id')
在这里,left表中有id字段,方法会自定遍历left表中的所有列名和index的level名称,如果与right表的index名称相匹配,则可以完成匹配。所以on参数可以不用指定。
但是,如果index的level名称,和列名,与right表中的index名称不同,则需要指定left表中的特定字段名称,与right表的index进行关联。
例子,left表的id字段改为了ids。
如果与right表的没有同名字段。所以关联时,需要重新指定
left.join(score.set_index('id'),on='ids')
如果与right表的没有同名字段。所以关联时,需要重新指定
left.join(score.set_index('id'),on='ids')
how
关联方式
lsffix和rsffix
如果遇到同名的列,通过lsffix和rsffix分别为重复的列名加前缀。
sort
是否排序,True 按照on指定的index排序。
False不排序。
几个应用场景
index与index 连接
left.join(right,how='inner')
不需要指定on参数
key 列 与 index 连接
left.join(right,on='key',how='inner')
left表的key字段与right表的index,存在逻辑上关联
如果right与left关联
right.join(left,on='key')是错误的!
正确的思路,被连接的表,关联的必须是index
right.join(left.set_index('key'))
结果
例子
坐标的columns 与右表的index 进行关联
单索引与复合索引连接
注意事项:如果一个单索引的表与另外一个复合索引的表进行关联时,一定要确保两个表的索引有同名的name属性
如图:当单一索引的表与复合索引的表,
通过index,进行关联时,关联的逻辑是基于index名称的。
关联名称不同,或者没有名称,都会导致关联抛出异常。
通过index,进行关联时,关联的逻辑是基于index名称的。
关联名称不同,或者没有名称,都会导致关联抛出异常。
异常的情况一
left表的index名称为key1 时,无法和right表的复合索引【两个level的名称为key和Y】进行关联。
left表的key没有name属性,也无法关联。并抛出ValueError: cannot join with no overlapping index names 异常。
如何解决:对调顺序即可。
有复合索引的表.join(单索引表,on='复合索引中某一个level的name')
注意事项2:不支持多对多的关联查询。
抛出Index._join_level on non-unique index is not implemented异常。
两个复合索引连接
例子1【两边的复合索引,level和名称一样】:
图,当两个复合索引name属性一致时,left.join(right)直接拼接,关联算法会比较两个表复合索引的元组,关联输出。
一个例外情况,需要注意:当left.reset_index()后,使用,left.join(right,on='key')会抛出,关联字段与right,索引level不匹配的异常,所以不想抛出异常,on关键词需变为,left.join(right,on=['key','key1'])
结果:
结果:
例子2【两边复合索引,level和名称不一样】
例子
直接用left.join(right)会返回结果,但是直接用left.join(right,on='key')会抛出异常
缺省on参数,join关联的算法,并不是直接比较两个表复合索引所组成的元组,相同返回结果,不相同返回结果。
join和excel的vlookup以及最新的xlookup
VLOOKUP (lookup_value, table_array, col_index_num, [range_lookup])
vlookup函数详解
vlookup函数的解释,
用vlookup实现两个表的拼接。
excel,vlookup返回的结果
excel返回的结果
vlookup的表格拼接,适合1:1关系和1:m关系,如果m:1,m:m关系,不会报错,
但结果不准确。lookup_value中有两个相同的值,匹配只用第一个匹配,第二个不会匹配。
但结果不准确。lookup_value中有两个相同的值,匹配只用第一个匹配,第二个不会匹配。
join返回的结果
子主题
pd.merge_asof()
近似匹配,了解一下
一般用在使用datetime作为索引的数据集中。
数据变形
pivot
数据透视的理解:
原始数据
原始数据需要保证每一行的数据是不一样的,这样才能继续透视。所以一般在读取数据前,可以运行drop_duplicates()
子主题
piv1 = df.pivot(index='日期',columns='购买者',values='金额')
统计每个购买者花了多少钱,每天花了多少钱。从逻辑上,同一天可能有两位购买者买东西,如果这样的话,如果用日期作为索引,值并不是唯一,不符合数据透视的条件。
piv2 = df.pivot(index='日期',columns='购买者',values='类型')
透视结果:购买者每天的消费项目。
参数
index
指定索引的列
注意:index和columns组成的数据对,必须是唯一的,否则不能透视。如果不是唯一可以使用pivot_table
columns
指定原数据表中的某一列,并将这一列里的所有值,作为数据透视表的列。
values
指定原数据表中的某一列,并将这一列的值,定位到对应的数据透视表中的index和columns。这个位置基于,原数据表中的value这一行的index和指定列(透视表中指定的那一列)对应的值。这个值,在透视表中变成了列名。所以,如果要成功透视,values 对应的行和列的数据必须是唯一的。pivot要求index必须是唯一的。
pivot_table 数据透视表
数据透视表
主要参数:
index = ‘购买者’
与pivot不同,pivot_table不要求值唯一,当index对应多个值时,通过aggfunc, 可以对这些值进行处理。
可以指定多个index,
例
图
columns = ‘类型’
选择列,并将列的值,聚类后,成为透视表的列(字段)。也可以不选列,直接按value和index进行统计。
values = '金额'
选择要计算或者要统计的列。
aggfunc = aggfunc
当index索引多个值时,指定处理的函数。传入的参数是一个series
例
表
fill_value
空值的处理,指定字符替换空值 NaN
margins = True
显示汇总列和汇总行。计算方式,受aggfunc控制。
margins_name = “汇总”
指定汇总列、行的名称,默认为all
stack():将列索引,转秩成行索引
作用和场景:
如果遇到类似这样的数据结构时,可以将列,变成索引的一部分。其实将数据集的横向改为纵向,或者为转秩。
转换前
通过转换数据变成如下的格式
转换后
通过删除na,可以得到如下结果df.stack(dropna=True)
主要参数:
level
默认是-1,最后一个level
如果列也是一个复合索引,可以通过level来指定,哪一个level用来转秩
例子:
表:
df.stack(level='年度')
结果:
df.stack(level='季度')
图
dropna
True
删除空值
unstack():将行索引转换成列索引
作用和场景:同stack类似,
主要参数
level
fill_value
df.unstack(level='部门')
结果:
df.melt() 将部分列转换成行
作用和场景
主要参数
id_vars
不参与转换的列
value_vars
参与转换的列
如果不指定,则除id_vars指定的列外,都会参与转换。
var_name
给新生成的参数的列,新命名。
value_name
给新生成的值的列新命名。
col_level
如果是列是复合索引,则需要指定。
如果不指定,且列是复合索引时,在选择id_vars和value_vars需要注意,每一个列的索引值实际上是一个元组。
pd.wide_to_lang 将列转换成行,并且支持对同一列的列名进行拆分。
作用和场景
例子
如图示dataframe
如果每一列呈现出一定规律,且能反应一定的数据关系时,可以使用wide_to_long函数。
比如,上面的例子,可以将列拆分成,[colA,colB],[1,2,3,4,5],也可以拆分成[col],[A1,A2,A3,A4,A5,B1,B2,B3,B4,B5]
[colA,colB],[1,2,3,4,5]
pd.wide_to_long(df,stubnames=['colA','colB'],i='user',j='参数')
stubnames:根据列名的规律,来拆分列名。
j 代表的是,被拆分后,后面的参数,本例【1,2,3,4,5】为被独立出一列,这个列名有j来指定,这一列的每一行,是拆分后的参数构成。并与每个index对应。
i 对应的索引。需要注意的是如果要进行这样的拆分,务必保证index是不重复的,否则会报错!
col,[A1,A2,A3,A4,A5,B1,B2,B3,B4,B5]
pd.wide_to_long(dfw,stubnames='col',i='user',j='参数',suffix='[A,B]\d+').sort_index()
注意:没有wide_to_long的方法,即 df.wide_to_long 并不支持。
如果遇到比较复杂的列名,且列名呈现出一定规律的时候,可以考虑使用这个函数。
参数
df
stubnames
拆分的列名的根命。
如果列名呈现的规律为,colA1……,colB1……
可以定义 stubnames=['colA','colB']
被拆分的,将被转秩成行的,为1,2,3,4,5
也可以定义stubnames='col'
被拆分的,将被转秩成行的,为A1,A2,……B1,B3……
i
指定某一列为索引列。
如果原来的dataframe有index, 且想指定原有index为该索引的话,原来的index要重置,通过reset_index(),把索引index变成一列,然后再来指定。该函数,不支持指定已经存在的index。
指定index列的每个值,必须保证是唯一的。否则无法转秩。
j
为转秩的新列,指定一个名字。这个新列里的数据,实际上是被拆分的列名的后半部分。
这一列,会和指定的i一起,构成一个复合索引。(i,j)
没有默认值,必须要指定一个新名字。
sep
指定列名的分隔符。
如 colA_1,如果指定sep='_'
就会被拆分成 colA,和 1
suffix
匹配符。使用正则表达式
如全部为数字
‘\d+’
如果有字母也有数组
‘\w+’
其他的匹配规则,可以参照正则表达式。
pd.crosstab() 创建交叉表
作用和场景
参考文章
思想:可以将一个表按照列,拆分成多个series ,然后指定那些列的内容可以构成行索引【重复值将会被聚类】,那些列的内容可以构成列索引【重复内容会被聚类】。那些列的内容可以构成值。有点类似数据透视表。但是该函数融合了一些统计分析的功能。可以方便的进行相对频率的分析。normlize = all,index,columns
例子:
原数据
# 统计购买者,不同该买类型的次数。
pd.crosstab(index=df['购买者'],columns=df['类型'])
结果:
# 统计购买者,不同购买类型的总金额
pd.crosstab(index=df['购买者'],columns=df['类型'],values=df['金额'],aggfunc=np.sum,margins=True).round(2)
子主题
主要参数
示例数据
原始数据
index
指定行索引
可以是一个序列,一个series。也可以是由多个序列或者series组成的列表。后者会返回一个复合索引。
示例:
指定一个序列作为行索引。
pd.crosstab(index=a,columns=b)
结果:先根据行、列组合。然后聚类,因为没有指定value,聚类只是统计行列组成的数据中,相同数据的个数。如上图 (bar,one)一共有4个,(bar,two)一共有1个。
指定多个序列作为行索引。
pd.crosstab(index=[a,b],columns=c)
结果:index是复合索引。没指定value,聚类同样为计数。
columns
指定列索引
可以是一个序列/Series,或者由他们组成的列表。
示例:
多个序列作为列索引。
values
指定值索引
示例
pd.crosstab(index=a,columns=b,values=d,aggfunc=np.sum)
结果
指定value的序列,不只是数字,也可以是其他格式的数据,只需要通过aggfunc进行对应分析就可以了。
pd.crosstab(index=a,columns=b,values=c,aggfunc=lambda x:str(x.to_list())[1:-1])
结果:
注意
注意:值只能是一个序列。
index,columns,values指定的序列,必须是等长的。
与pivot_table不同,crosstab函数,并没有默认的aggfunc函数。所以如果要指定value,就必须要指定aggfunc函数。
colnames
指定列索引的名称,根据columns传入序列的个数。如果是两个指定两个names
pd.crosstab(index=a,columns=[b,c],colnames=['A','B'])
rownames
同上
当行索引只有一个序列时,如果指定名字,也要用列表框起来,不然的话,会被解析成多个列名。
pd.crosstab(index=a,columns=b,rownames='AB')
这样会报错,因为会被解析成'A'和'B',
aggfunc
指定聚类分析的函数。同pivot_table
margins
同pivot_table
margins_name
同pivot_table
dropna
删除都是NaN的行。
normalize
各个数据所占的百分比。
all
根据全部数据计算百分比
index 或者 0
根据行计算百分比
column或者1
根据列计算百分比
True
计算包含了margins
cut 数据分级【分段】,用于直方图分析
应用和场景
参考文章
比如:根据销售列表,将价格区分成几个价格段进行分析
比如:根据学生的成绩,将其分成[0,60,75,85,90,100]【不及格,及格,良好,优秀、非常优秀】
图
可以通过plot画出成绩分布图
主要参数
x
进行分段的数据序列。数组、series,list等均可。
bins
分段的依据
整数
均分成多少段。
数组
[0,60,75,85,95,100]
IntervalIndex对象
pd.IntervalIndex.from_breaks([0,60,75,85,95,100],'left')
right
定义分段的节点
True
(0,60]
False
[0,60)
labels
定义每个分段的名称
rebins
是否返回,定义的bins
precision
如果bins定义的为整数。计算分段时,精确到小数点几位。
include_lowest
是否包含最小值。
关于分段数据设置的问题。
比如成绩分布的问题。[0,60,75,85,95,100]
如果没有设置 right
那么分段为:[(0, 60] < (60, 75] < (75, 85] < (85, 95] < (95, 100]]
0分将不会被统计,60分也会被认定为不及格。
解决方案:right=False ,不包括右边数据。
新的分段:[[0, 60) < [60, 75) < [75, 85) < [85, 95) < [95, 100)]
此时,100分将不会被统计。
所以最好的方式:[0,60,75,85,95,100.1],right=False
include_lowest和上面的思路一样,如果将incluede_lowest设置为True
没有设置的情况:[(0, 60] < (60, 75] < (75, 85] < (85, 95] < (95, 100]]
设置为False, 最下限0,没有被统计。
设置为True,[(-0.001, 60.0] < (60.0, 75.0] < (75.0, 85.0] < (85.0, 95.0] < (95.0, 100.0]]
这样最下限的值,0分就可以被被统计
duplicates
raise
抛出异常
drop
删除重复值。
[0,60,60,75,85,95,100]
设置为raise,将抛出异常。
get_dummies() dummy编码。这个会在机器学习中用到
具体可关注:
factorize() 去重、分类
有pd函数,series和index方法。有点类似numpy的unique。一个序列中相同的元素会被聚类成一个。
有两个返回值:
第一个返回值,和原数列序列等长的序列,每个值对应的是第二个返回值【也是一个序列】对应的index
第二个返回值:uniques,将相同元素归类后,返回的序列。
应用和场景
在机器学习中的运用。
简单数据分析中的运用
在一张销售表中统计有多少种产品,以及分别的名称。
表
sales['name'].factorize()[1]
Index(['Barton LLC', 'Trantow-Barrows', 'Kulas Inc',
'Kassulke, Ondricka and Metz', 'Jerde-Hilpert', 'Koepp Ltd',
'Fritsch, Russel and Anderson', 'Kiehn-Spinka', 'Keeling LLC',
'Frami, Hills and Schmidt', 'Stokes LLC', 'Kuhn-Gusikowski',
'Herman LLC', 'White-Trantow', 'Sanford and Sons', 'Pollich LLC',
'Will LLC', 'Cronin, Oberbrunner and Spencer',
'Halvorson, Crona and Champlin', 'Purdy-Kunde'],
'Kassulke, Ondricka and Metz', 'Jerde-Hilpert', 'Koepp Ltd',
'Fritsch, Russel and Anderson', 'Kiehn-Spinka', 'Keeling LLC',
'Frami, Hills and Schmidt', 'Stokes LLC', 'Kuhn-Gusikowski',
'Herman LLC', 'White-Trantow', 'Sanford and Sons', 'Pollich LLC',
'Will LLC', 'Cronin, Oberbrunner and Spencer',
'Halvorson, Crona and Champlin', 'Purdy-Kunde'],
主要参数
values
如果是pandas函数,values是一个一位数组。或者series
sort
排序
na_sentinel
对NA数据的标记。
size_hint
explode() 如果series是
如果series的元素是列表,使用explode()
如:ADMA数据集,featurecolumns_1:最近3天,点击品牌的userid。
比如:
datas['featurecolumns_1'].str.split(',').explode()
对每一行的列表进行扩展,并根据index生成新的series
子主题
处理文本数据
基本的文本操作
大小写转换
s.str.upper() 全部大写
s.str.lower() 全部变小写
s.str.title() 首字母大写
s.str.capitalize() 一段字符串的首字母大写。
s.str.swapcaes() 大小写互换
s.str.casefold() 消除大小写
消除大小写
字符填充
s.str.center(width,fillchar=' ' ) 根据width,插入指定【fillchar】,原字符串居中
++++ab++++
s.str.ljust(width,fillchar) 根据width,插入指定字符,原字符左对齐
ab++++++
s.str.rjust(width,fillchar) 根据width,插入指定字符,原字符右对齐
+++++ab
s.str.zfill(width) 根据width,字符前插入0,如果遇到正负号的数字字符,在+-号后,数字前填充0
00012
+ 0012
000ab
s.str.pad()
删除指定字符
s.str.strip(to_strip=None) 去除指定字符【字符串两边】,不指定为空字符
移出指定字符。如果不指定,默认移出空格。
算法:从两边查找字符,属于指定字符的移出,直至两边都没有指定的字符。中间的字符,并不影响。
例子:
"abcdada".strip('ad') = "bc"
例子2
修改有空格的列名。
相关移除的方法
s.str.lstrip()
s.str.lstrip()
s.str.rstrip()
字符串的拆分和组合
组合
s.str.cat(others=None,sep=None,na_rep=None,join=None)
将series同dataframe,series,数组、序列。按照指定的字符进行拼接。
sales['name'].str.cat(sales['sku'],sep=' 型号:')
sales['name'].str.cat(sales['sku'],sep=' 型号:')
如果没有指定others,则会返回这个序列中所有字符的拼接。根据指定的字符。
sales['name'].str.cat()
结果:
主要参数
other
其他series、dataframe、ndarray等。或者是这些元素组成的列表
例子
数据sales
需求,将name和sku拼接,组合成一个新列。
sales['name'].str.cat(sales['sku'],sep=' # ')
结果
s.str.cat([s1,s2,s1.values,s1.to_numpy],sep='_',na_rep='#')
结果
注意:
如果是ndarray,ndarray的长度必须和s的长度一致,否则会抛出异常。
但是series和DataFrame,要自由一些【如设置了join参数。不等长也可以,因为数据会基于index对齐】,
如果没有指定连接的series、dataframe 的话,cat()函数会将series本身的字符拼接到一起。
例子
去掉重复的数据后,然后再对series本身进行拼接:将所有字符串按照 “ # ”为分隔符。拼接成一个字符串
sep
连接字符
na_rep = 'missing'
NA数据的处理
如果other没有指定,series中的NA将被na_rep指定的字符替代。
如果other指定。则先进行对齐,如果出现不对齐的情况,会出现NAN,如初指定了na_rep,这里的NAN会被替换成指定字符。
例子:
join
{‘left’, ‘right’, ‘outer’, ‘inner’},
对齐处理
left保留左边
right保留右边
inner 不对齐的全部去除
outer 不对齐的全部保留,并显示NAN
注意:如果other也是series,且index不一致的话,需要留心!
当前如果不设置join参数的话,对齐会根据series的实际顺序,而不是index,同时会报警!
如图
如果设置了join=‘left’参数,结果为变成
会默认对齐index,保留了s1 的信息,但是s2没有匹配的字符,所以显示NaN
如果不想显示NaN,可以设置na_rep
如果不想显示NaN,可以设置na_rep
s1.str.cat(s2,join='left',na_rep='_')
s.str.join(sep)
如果series中每一个元素是一个可迭代对象,可以通过join,把可迭代对象每一个元素按照指定sep拼接成一个字符串
s['s1'].str.join(sep='_') 当元素是一个列表
结果
s['s1'].str.join(sep='_') 当元素是一个字符串
结果
如果列表中有其他类型数据,将会返回NaN
如果遇到不可迭代的元素,会返回NaN
结果
sep必须要定义
拆分
str.split(pat,n=-1,expand=False)
根据分隔符,将series中的文本按照指定的分隔符进行分割。默认返回list
分隔符也支持正则表达式
例子:通过正则表达式的环视功能,从身份证号中拆分残疾证号
代码:
进一步对结果进行分析处理,比如统计各类残疾的人数
图
如果expand = True
则将结果拓展成一个dataframe
n
指定分割的次数。如果是-1 全部分割。如果指定为1,从左边第一个分隔符开始分割。分割到第一个就结束。
对应的还有srt.rsplit()方法。
n则是从右边开始
应用:如果这一列都是文件名。则可以通过
s.str.rsplit(n=1,expand=True)
分别获取文件名和扩展名。
查找相关
str.find(sub,start,end)
str.findall(pat,flag=0)
例子:利用正则表达式和findall实现数据清洗
原数据即要求:提取产业种类,并根据不同项目的预期单位收益,计算产业预期收益。
辅助表profits
思路:提取产业名称中所有非数字字符,并去掉重复的字符,并将其作为正则表达式的字符集。
提取所有非数字符 names = info['产业名称及规模'].str.findall(r'[^\d.]+').str.join('').str.cat()
结果
去掉重复的字符:
结果
根据这个字符集,通过正则表达式,拆分series中的文本。
结果
使用apply方法,先定义两个函数
根据‘养虾’,在profits表中['项目']列中的名称进行匹配
鸡在profits表中,会返回结果。
虾,不在profits表中,没有匹配结果。这个需要考虑。
根据上面info['产品名称及规模'].str.findall(pat)的结果,定义第二个函数,
遍历元素[(养鸡, 50, 只), (养鱼, 6, 亩), (优质稻, 3, 亩)],提取信息
根据第一个函数的结果,进行计算。
遍历元素[(养鸡, 50, 只), (养鱼, 6, 亩), (优质稻, 3, 亩)],提取信息
根据第一个函数的结果,进行计算。
info['预期收益'] = info['产业名称及规模'].str.findall(pat).apply(cal,profits=profits)
计算结果:
结果
flag定义正则表达式的匹配模式,如re.I【忽略大小写】
str.match(pat, case=True, flags=0, na=nan)
例子
查找全国地区代码,找到有‘幸福’的地名。
注意:match的功能和re.match功能是一样的。
+ 从字符串头部开始查找,从头部开始符合正则规则,才会返回匹配结果
+ 如果字符串头部不符合正则规则,即使后面符合规则,也不会有匹配结果
+ 从字符串头部开始查找,从头部开始符合正则规则,才会返回匹配结果
+ 如果字符串头部不符合正则规则,即使后面符合规则,也不会有匹配结果
a['area].str.match(pat='安')
会匹配所有‘安’开头的地名
a['area'].str.match(pat='.*安.*')
会匹配所有含有‘安’的地名
也可以使用a['area'].str.contains(pat='安')
str.contains(self, pat, case=True, flags=0, na=nan, regex=True)
str.extract(pat,flag=0,expand)
例子
通过正则表达式提取身份证号码的地址码、出生年月码、顺序码、验证码、和残疾证码
正则表达式:pat2 = r'(?P<地址码>\d{6})(?P<出生年月>\d{8})(?P<顺序码>\d{3})(?P<验证码>[\dXx])(?P<残疾证编码>\d{2})?'
info['身份证号'].str.extract(pat2)
结果
有残疾证编码的
有问题的例子:将【产业名称及规模】列,分别提取名称、规模、单位。
结果:
注意:extrat的匹配原则是,找到即止,所以只返回第一组匹配的结果,后面即使满足条件,也不会返回结果。
str.extractall(pat,flag=0)
返回的结果
子主题
综合一下
代码
结果
子主题
替换相关
str.replace(pat,repl,n=-1,flag=0,case=None,regex=True)
主要参数
pat
字符串
正则表达式
正则对象
repl
替换字符
替换函数
n
替换次数,默认为-1,全部替换。
flag
正则匹配模式,如re.I
case
是否区分大小写,如果不适用正则的时候,考虑设置,如果用正则不用设置。
regex
是否使用正则,默认为TRUE,如果匹配规则简单,不需要使用正则,可以设置为False,这可以提高匹配效率。
例子:将身份证号码后四位替换成*,如果是20位,后6位也替换成*
结果
索引
s.str[1] 索引
切片
s.str[0:6]
s.str.slice(0,6)
处理缺失数据
关于NaN
关于None
如果series的其他元素数据类型为数值类型,None会被转换为NaN
子主题
如果其他元素为object,None不会转换
子主题
如果其他元素为datetime,None会被转换为NaT
NaT
空数据,都会被认定为NaN
NaN的类型
numpy和pandas会将NaN认定为float。
这会使其他类型为int的数据,都会转换为float
index本来是int类型,但是因为存在NaN,就被转换成了float
解决方法:将数据类型定义为 dtype=pd.Int64Dtype()
子主题
更方便的方法:dtype='Int' I一定要大写,小写会抛出异常
定义类型为小写int会抛出异常
定义为大写Int就可以了
NaN的布尔运算
两个NaN是不一样的,所以 NaN == NaN 的结果为False
不能用 d[d['id'] == np.nan] 或者 d[d['id'] != np.nan] 这样的布尔索引!这样会无法定位NaN!
数据
并不能定位NaN的位置
如果想定位NaN。使用isna()和notna()方法。
用isna()定位NaN的位置
用notna()定位非NaN的位置
python的None类型,是一样的,None == None 的结果为True
NaN的数学运算
NaN和任何数字的数学运算的结果,仍然为NaN
df['d'] = df['a'] + df['b'] + df['c'] 结果仍然是NaN
NaN参与统计运算时需要注意!
sum/mean等统计计算中,默认跳过NaN数据。skipna=True。
当DataFrame或者series全部为NaN时
数据
子主题
sum()的结果为0
d['na'].sum() =0
mean()的结果依然是nan
子主题
prod()的结果为1
子主题
cumsum()的结果依然为NaN
子主题
关于groupby
NaN 类型的数据在groupby分组时,会自动忽略。
不会出现 NaN 16 的结果,因为在分组计算时会被忽略。
处理NaN
替换和填充
df.fillna()
values
具体值
d.fillna(0)
dict
d2.fillna({'a':0,'b':1,'na':'missing'}) 对每一列NaN分别替换
series
同dict,如果index与列名相同,则用对于的值进行替换。
DataFrame
如果一个shape和列名都一样的情况
d2 的NaN会被d3对应位置的数据替换。
如果一个shape不一样的情况:找到就替换,找不到保留NaN
d2.fillna(d3.iloc[:-1,:]) d2 在d3中没有找到第四行对应的值,所以就不替换。
列名不一样:找到就替换,找不到就保留
子主题
method
如果想用原DataFrame里的值来替换,可以指定method
method的方法有
ffill或pad
用NaN前面的数据填充
用NaN前面填充
对应的独立方法
ffill()
backfill或者bfill
用NaN后面的数据填充
子主题
对应的独立方法
bfill()
limit
限定替换次数
df.interpolate()
method
插值方法:默认为linear【线性】。
linear
忽略index,将NaN两边的数据按照均匀分布计算。
time
如果index为datetime类型,那么使用time方法,可以依据时间为x轴,数值为y 轴,按照线性的方法,计算对应的插值
linear和time的比较
如果把2019-01-09改为2019-01-19那么结果会变为3.1538
index,values
计算方法同time,会先计算两个index之间的差值。然后根据NaN两边的值,计算均匀分布
根据NaN两边index,计算等分(8-4)+1=5。在计算4到20的均匀分布数列。index7对应这个数列中第四个值。即16
interpolate.interp1d支持的方法
测试代码
不同插值方法的曲线
linear
nearest
zero
cubic
interp1d支持的拟合曲线中,比较平滑的有
cubic 三次插值
quadratic 二次插值
深度阅读
barycentric 重心坐标拉格朗日插值
会出现龙格现象。
如图
akima
对应的scipy函数:scipy.interpolate.Akima1DInterpolator(x, y, axis=0)
拟合曲线比较平滑
示意图
子主题
代码
piecewise_polynomial : 分段多项式插值
spline 三次样条插值
如果数据有一定的噪音,使用UnivariateSpline方法,可以消除噪音
代码
图例
pchip 分段立方插值
PCHIP则可以保证三次样条局部单调性
krogh 埃尔米特插值
pandas的建议
在做数据处理时,不同类型的数据,填充NaN适用不同的方法
如果处理一个快速增长的时间序列,可以使用【quadratic】
如果你有一个近似累积分布函数的值,可以使用【pchip】
如果想要比较平滑的拟合曲线,可以使用【akima】
不同方法的比较
子主题
axis
填充的方向,沿着行或者列填充
limit
如果出现连续NaN,limit限定能够填充连续NaN的最大数目
limit
如果出现连续NaN,limit限定能够填充连续NaN的最大数目
如果出现连续NaN,limit限定能够填充连续NaN的最大数目
例子
原数据
d.interpolate(method='values',limit=2)
最多填充两个连续NaN
limit_direction
如果限定了limit,则limit_direction表示连续NaN的填充方向:forward,backward,both
limit_area
如果限定了limit,limit_area表示处理NaN数据相对所有有效数据的位置。
inside
所有有效数据内部填充。
outside
所有有效数据外部填充
None
不限制
比较area的不同结果
子主题
删除NaN
df.dropna()
主要参数
axis
默认axis=0 或者 axis='index'沿着行查找是否有NaN数据
axis=1 或者 axis='columns',沿着列查找NaN
使用dropna()的注意事项
数据
每一行都有NaN
沿着行查找,如果有NaN就删除,所以使用df.dropna(),会删除所有数据
子主题
解决办法:先沿着columns方向查找,如果全部都是NaN的列,先删除,然后再删除含有NaN的行
思路 df.dropna(axis=1,how='all').dropna()
how:删除方式
'any'
只要有NaN就会删除对应的行【或者列】
‘all’
全部是NaN才会删除对应的行【或者列】
thresh:最少保留多少个非空数据
例子
原数据
子主题
df.dropna(thresh=1) 最少保留1个非空数据。第三行会被删除
子主题
df.dropna(thresh=2) 最少保留2个非空数据,第2和第3行会被删除
子主题
df.dropna(thresh=3) 最少保留3个非空数据,1234行都会被删除
子主题
subset:制定查找NaN的列或者行
注意:如果是沿着行查找,subset 指定的是列名。
例子
原数据
df.drop(axis=0,subset=['a','b','c'])
只在a、b、c三列寻找,沿着行查找。不属于【a/b/c】的na列有NaN数据也会保留
子主题
df.dropna(axis=1,subset=[0,1,2,3])
只在0,1,2,3行,沿着列查找,即使第4行,有NaN也会被忽略
子主题
如果是沿着列查找,subset指定的是index
inplace:是否改变原DataFrame
replace方法
df.replace()
主要参数
to_replace 查找的数据
数值、字符串、正则表达式
数值
r1.replace(10,910)
字符串
r1.replace('jim','x')
正则表达式:注意regex=True
例子:根据表达式,将字符串位数为3的替换成ooo
列表
如果to_replace 和value都是list,则两个list必须等长。
子主题
如果不是,value可以是一个值。
将19和12都替换成了NaN
如果是一个由正则表达式构成的list,
需要注意:
会根据list中的正则表达式依次解析,
如果value也是一个列表,则解析一次,替换一次。
需要注意:
会根据list中的正则表达式依次解析,
如果value也是一个列表,则解析一次,替换一次。
还需要注意的时,第一次替换完成后,也会改变df的数据,再次调用第二个正则表达式时,
如果被替换后的字符串,依然符合后续的正则规则,还会被进一步替换,直至到达列表结束。
如果被替换后的字符串,依然符合后续的正则规则,还会被进一步替换,直至到达列表结束。
字典
df.replace({'a':'b'})
三种情况
df.replace({'a':'b'})先查找数据中是否有字符‘a’,如果有将字符‘a’替换成‘b’
df.replace({'a':'b'},'B') 如果指定了value值,则是先寻找是否有a列,如果有,再从a列中寻找字符b,再将其替换成B
df.replace({'a':{'a':'b'}}) 没有指定value值,且是这种引用方式的话,则表示在a列中寻找字符a,如果找到将其替换为b
value
根据to_replace的定义,确定value
limit
限定替换次数
regex
是否支持正则表达式
inplace
是否改变原数据
method
pad/ffill/bfill
分类数据
认识categorical data类型
categoricals是pandas处理分类变量的一种数据类型,如性别、血型、评级等等
categorical的原理:
如果某一列是categorical类型,那么包含了两个主要属性
例子
grade列是categorical类型。grade列表面上显示的是[非常优秀,优秀...]这样的字符串,实际上是categories分类序列中的索引。
第一个:分类数据【CategoricalDtype】,如成绩等级,一个类似集合类型【没有重复】的序列
第二个:codes数据,遍历这个列的每一个元素,codes是这一元素在分类数据中的index
categorical解析
子主题
分类数据在以下场景中非常有用
如果有一个类型为字符串类型的列,这一列中的字符串实际上也就几种。如果将这一列转换为categorical类型,可以节省存储空间。
比较正常读取excel每一列的大小,和将类型改为category占用的内存大小。如果数据量大,节省更明显。
可以自定义数据的排序规则
例子
原数据
原数据:虽然3列都一样,但是创建方式不一样。重点关注【等级2和等级3】列。
等级2是普通的字符串列。等级3列是由pd.categorical创建的序列。
等级2是普通的字符串列。等级3列是由pd.categorical创建的序列。
一个正常的排序
会默认按照第一个字符的unicode顺序排序排序,所以不会按照【不及格、及格、良好、优秀、非常优秀】的期望来排序。
s.sort_valuse('等级2')
通过定义分类数据,自定了排序规则
期望的排序为【不及格、及格、良好、优秀、非常优秀】
结果:与自然排序相比,通过定义categorical,可以按照我们的期望进行排序。
创建分类数据实例方法
创建类型为category的series方法
创建category实例,pd.Categorical(grade_list,categories=['不及格','及格','良好','优秀','非常优秀'])
第一步创建categorical 序列:c = pd.Categorical(s,categories=['不及格','及格','良好','优秀','非常优秀'],ordered=False)
第二步创建series,并将category实例作为数据。s['等级2'] = pd.Series(c)
一个有意思的创建方法:pd.Categorical.form_code(code,categories)
一个例子:创建一个随机抽奖函数
子主题
函数运行结果
将现有的series的类型改为category
s.astype('category') 修改series的dtype为category。
原series
s['等级2'].astype('category')
结果
创建DataFrame,批量或者指定每一列数据类型为category类型
例子,读取excel,并指定相应列为category类型
hn = pd.read_excel(r'../datas/hn.xlsx',header=2,dtype={'项目名称':'category','项目简称':'category','相关处室':'category'})
也可以批量修改DataFrame所有列的数据类型为category类型
注意astype,会返回修改后的原DataFrame映射。而不是原来的DataFrame。
创建一个CategoricalDtype
注意:category和CategoricalDtype的区别。
category是一个分类数据实例,包含了数据和分类数据。
定义后,可以将这个数据作为某个series的数据源。
pd.Series(category实例)
CategoricalDtype是一个分类数据类型实例,只包含分类依据数据,并不包含数据。
定义后,可以将这个分类数据类型实例作为某个series的dtype
pd.Series(data,dtype=CategoricalDtype实例)
应用,通过指定自定义的categoricalDtype,有两个好处
如果直接指定series的的type为category类型,分类数是根据series内容自动生成的。无法自定义额外categories或者去除部分categories内容,且这个category的顺序是基于系统排序,而不是我们期望的排序。
直接创建包含数据的category实例,再创建series,流程要复杂点,对于已经存在的series,可以自己定义CategoricalDtype实例,并将dtype定义为这个实例即可。
创建:c1 = CategoricalDtype(categories=[项目分类列表],ordered=False)
categories
定义分类数据的分类依据
ordered
是否是有序关系分类
注意:这个并不是用于排序的。
而是从语义上区分两个categoricalDtype实例是否相同。
如果定义为false
当ordered为False时,categories不是有序关系,所以两个categoricaldtype实例是相同的。
当ordered为True时,categories是有序关系,所以两个categoricalDtype实例是不相同的。
有些类似list和set 的关系。set是无序的,list是有序的。
子主题
分类数据的操作
和字符串类型的Series用str操作符一样,分类类型的Series用.cat操作符
注意:categorical 序列和类型为categoricalDtype的series之间的区别。
categorical 是pandas一种特殊类型的序列,本质上和list/set/元组,np.array一样。
就像datetime类型数据不要dt操作符一样,categorical 序列不需要.cat 操作符!
数据类型为categoricalDtype的series序列,本质上是series。为了便于操作不同类型series,pandas提供了相应的操作符。
.str 操作字符串相关属性和方法
.dt 操作时间类型的相关属性和方法
.cat 操作category类型的相关属性和方法
.cat.categories 【返回分类索引】
s.cat.categories 返回分类索引
注意:categorical类型的series,使用s.cat.categories 返回的结果和s.unique() 返回的结果可能一样。但是原理不一样
s.cat.categories
如果将一个categorical 序列作为series数据,那么,s.cat.categories,返回是这个categorical定义的categories
例子
s.unique()
如上面的例子:categorical在定义时,categories 定义了一个‘额外’。使用unique()返回的结果中并没有‘额外’这一数据。
.cat.ordered 【是否有序】
True
可以进行 min(),max() 等包含逻辑判断的方法。
False
如果进行逻辑判断,则会抛出异常。但是可以正常使用sort_values方法。排序依据是categories列表的顺序。
注意:如果使用修改类型为category的方法,转换的categorical序列,默认ordered为False。
可以通过.cat.as_oredered() 方法转换为True
修改categories
s['等级3'].cat.categories = ['C','B','B+','A','A+','E']
修改前后
子主题
series数据本身是每一项对应的categories的index,显示的是categories数据投影。当改变categories时,对应的,series显示的数据也会发生改变。
s['等级3 '].cat.rename_categories( ['C','B','B+','A','A+','E'])
rename_categories()的几种参数形式
第一种,和原来categories等长的list
第二种:使用{'原cat','新cat'}的字典
s['等级3'].cat.rename_categories({'不及格':'C','及格':'B','良好':'B+','优秀':'A','非常优秀':'A+'})
s.cat.set_categories(['two','one','-','four'])
注意:和s.cat.categories = ['one','two','-','four']结果的差异
直接修改categories属性值,对应的series也会发生改变,因为series中的数据序列,只保存了对应categories序列的index。当index发生改变,数据序列的表现也会发生改变。所以直接设置categories属性时,一定要慎重。容易因为改变categories属性而造成整个数据序列的数据逻辑发生改变。
使用set_categories方法,如果rename=False,无论categories如何修改,原series的顺序是不会发生改变
s.cat.set_categories(['one','two','-','four'])
另外一个例子
原df
子主题
修改series 数据类型
df['标准'].astype('category')
categories 不符合逻辑需要修改。
符合逻辑的categories应该是 ['偏瘦','正常','偏胖','肥胖','重度肥胖','极重度肥胖']
一定要使用s.cat.set_categories()来修改
没有使用的后果:series的显示顺序改变了,逻辑结构也发生了改变。
修改categories的注意事项:
尽量不要用直接给categories 赋值的方法修改 categories
不能是重复的数据、不能是NaN
增加categories
s['等级3'].cat.add_categories('非常优秀E')
结果
删除categories
s['等级3'].cat.remove_categories('额外')
修改前后
对比
删除没有用到的categories
s['等级3'].cat.remove_unused_categories()
categories 排序问题
有序和无序的区别
无序的categorical
一般地通过改变series数据类型为category时,series在逻辑上是无序的。
虽然可以使用sort_values,排序依据是categories参数指定的顺序,
但是不能使用,min(),max() 等带有逻辑比较的方法。会报错。
虽然可以使用sort_values,排序依据是categories参数指定的顺序,
但是不能使用,min(),max() 等带有逻辑比较的方法。会报错。
有序的categorical
当categories 显示有< 时,说是是有序的。有序的categorical 可以使用逻辑比较和运算的方法。min,max等
如何修改categorical 的有序无序属性
as_ordered()
改为有序,此时categories 会发生一些改变,也可以使用min(),max()等带有逻辑比较的方法。
as_unordered()
变为无序
修改排序
reorder_categories()
注意:使用reorder_categories,只修改顺序,并不修改内容。如果想修改内容和顺序,可以使用set_categories()
注意:如果是一个有序的categories,新增一个categories选项,会在这个列表后添加新的选项后,排序关系可以看看这个例子
新增4和5 会在categories 列表后添加,order关系会呈现如上关系
注意:所以,如果给已经排序的categories 新增和删除元素,注意看一下,新增、删除后的排序关系!
categories 的逻辑运算
可以进行逻辑运算的前提是 ordered = True
简单逻辑运算
两个series 进行逻辑运算时需注意
普通类型的series是不能和category类型series进行逻辑运算。
如果两个series类型都是category,那么categories 必须是一样才可以进行逻辑运算
当categories 不同,会抛出以上异常。
建议:在指定某个series为category类型需要注意,如果后面需要逻辑运算,
则需注意,是否有不同series 之间的逻辑运算,如果有,需要慎重考虑.
则需注意,是否有不同series 之间的逻辑运算,如果有,需要慎重考虑.
简单逻辑运算
简单逻辑运算
例子
在这需要注意:为什么使用 df['children'] 小于0,会得到大于0的结果。
因为在执行逻辑运算时,给定的条件会和categories的列表进行逻辑运算。
此时给定的条件,0>3/4/5/2 所以会返回3452的结果,而不是实际上小于0的结果。
此时给定的条件,0>3/4/5/2 所以会返回3452的结果,而不是实际上小于0的结果。
在这需要注意:为什么使用 df['children'] 小于0,会得到大于0的结果。
categories的一些常用操作
s.value_counts()
例子
对categories进行统计运算,省去使用groupby在count的麻烦
主要参数
normalize = False
如果等于True,返回占比。
sort = True
默认会对value计数结果进行排序,设定为False,则不排序。
bins
如果不是category类型的series,可以指定bins,对数值进行统计。仅对数值类型的series有效。
ascending = False
升降序
s.groupby()
例子:series中有没有用到的 unused_categories【series中没有显示,但是categories里有的数据】时,使用groupby时,也会计算
子主题
df.groupby('标准').mean()
切片操作,并不影响categories
虽然在series中没有显示,但是categories依然会保存。
category 类型的series 可以直接使用.str,.dt连接符,直接操作文本和datetime
修改或者赋值 category series
如果新赋的值不在categories里,则会报错
拼接
如果按照行的方向拼接两个含有category类型的DataFrame时,如果两个category中的categories不同,不会报错,但是拼接后的类型将不再是category
使用df1.append(df2)也是一样的结果。
使用df1.append(df2)也是一样的结果。
子主题
如果拼接的category序列,categories属性是一样的,那么拼接后就不会转变成str。
两个df,category是一样的。所以拼接后的结果并没有发生改变。
不同类型category序列拼接后返回的结果
子主题
即使两个categories一样,但是一个有序,一个无序,返回的结果也是object
如果不同的category想拼接,并且返回的仍然是category可以使用
union_categoricals()函数
例子
注意:使用该函数,会改变categories的codes,如果数据与codes有一定的关系,使用该函数需要注意
注意2:有序和无序的categories,如果 ignore_ordered= False 的话,会抛出异常
注意3:如果两个categories的数据类型不同,也不能组合。
CategoricalIndex
指定category类型series作为index。
例子:
categories 的逻辑运算
可以进行逻辑运算的前提是 ordered = True
简单逻辑运算
两个series 进行逻辑运算时需注意
普通类型的series是不能和category类型series进行逻辑运算。
如果两个series类型都是category,那么categories 必须是一样才可以进行逻辑运算
当categories 不同,会抛出以上异常。
建议:在指定某个series为category类型需要注意,如果后面需要逻辑运算,
则需注意,是否有不同series 之间的逻辑运算,如果有,需要慎重考虑.
则需注意,是否有不同series 之间的逻辑运算,如果有,需要慎重考虑.
简单逻辑运算
简单逻辑运算
例子
在这需要注意:为什么使用 df['children'] 小于0,会得到大于0的结果。
因为在执行逻辑运算时,给定的条件会和categories的列表进行逻辑运算。
此时给定的条件,0>3/4/5/2 所以会返回3452的结果,而不是实际上小于0的结果。
此时给定的条件,0>3/4/5/2 所以会返回3452的结果,而不是实际上小于0的结果。
在这需要注意:为什么使用 df['children'] 小于0,会得到大于0的结果。
数据分组 groupby
groupby三步走
拆分、计算、组合
示意图:
子主题
拆分 根据某种规则将数据拆分成不同的组。
经过拆分后,会返回一个GroupBy对象。
子主题
常见的拆分规则
常见的拆分,对gender列进行unique()后,按照unique的值的不同【规则】进行分组。返回的不同组数据的index
通过简单的索引,就可以得到某一组数据。
也可以自定拆分规则
通过函数自定义分组
拆分的原理
第一步:在默认情况下,会对序列2,进行数据分组,相同是数据会被分为一组,同时返回相同一组数据在序列2中的index。
序列2,可以是任何序列,list,np.array,series,dataframe等等。
序列2的前提条件,必须和序列1是等长的。
即便是两个不同的DateFrame只有等长,都可以分组。
两个不同的DataFrame,都可以groupby
默认是根据unique()分组。也可以自己定义分组规则,如上面edu的规则。
第二步:对序列2进行分组后,会得到不同分组的index序列。
序列2进行分组后,会得到 {相同数据:相同数据的索引} 的数据映射。
元素1 的 index 0,2,4
元素2 的i ndex 1,3
用这个分组index,索引序列1,就得到了分组结果。
元素1 的 index 0,2,4
元素2 的i ndex 1,3
用这个分组index,索引序列1,就得到了分组结果。
一个简单得到各个分组元素index的算法
第三步:再用这个不同分组的index序列,对序列1进行索引。从而得到分组的目的。
序列1,支持Series 和 DataFrame
序列2支持的类型
同序列1等长的list,array,series等
s.groupby(list)
s.groupby(np.array)
s.groupby(s)
序列2 可以是序列1自身
不管是何种序列,原则是和序列1等长。
函数调用,这个函数传入的参数是序列1的index。
如果是函数调用,传递的参数是序列1的index。可以自己定以对序列1index的操作规则
也可以操作序列1中的数据
df.groupby('family')
其实是pandas提供的语法糖。实际上是 df.groupby(df['family']) 这样更容易理解。
多个与序列1等长的序列列表
df.groupby(['family','gender']).count()
对df中的family列和gender列两个序列进行分组。
也可以是列表
对三个list进行聚类分析。
字典映射
如果index为复合索引,或者index也呈现出聚类的特征。则可以使用该方式
series的index呈现聚类特征,所以可以使用字典映射的方式,进行聚类。
原理类似,只是给通过字典映射,分别给abc起了个别名而已。
复合索引
序列1是复合索引的情况
数据
原始数据
如果序列2是与序列1等长的简单序列
根据['g_a', 'g_a', 'g_b', 'g_b', 'g_b', 'g_b', 'g_b', 'g_b', 'g_a', 'g_a']列表进行分组聚类,
得到g_a和g_b的index,然后索引df3。并进行聚类分析。
得到g_a和g_b的index,然后索引df3。并进行聚类分析。
可以通过get_group("g_a") 分组后的df3。
当使用sum聚类函数时,会对每个组的DataFrame 或者series 沿着行的方向,进行汇总计算,
最后将各个组的计算结果,拼接成新的DataFrame。
当使用sum聚类函数时,会对每个组的DataFrame 或者series 沿着行的方向,进行汇总计算,
最后将各个组的计算结果,拼接成新的DataFrame。
g_a组DataFrame每一列计算的结果。
汇总计算的结果
如果进行分组依据的序列2 就是序列1自身的话
如果复合index,各个level没有name属性,可以直接指定复合索引的level,进行分组索引。
如果复合索引的index,各个level有name属性。可以直接用name名称
如果分组依据是函数调用。那么传入这个函数的参数是复合索引的元组:('A', 'row_1'). ...。通过操作元组也可以实现分组效果。
子主题
如果分组依据是多个序列,则对多个序列进行zip后,在进行分组
例子
该例中会将col6作为一个序列,将g和col6 聚合后,相同项分组,并得到各个相同项的index序列。
需要注意的是,这个序列实际上是整数序列,而不是单纯索引col6得到的复合索引序列。
需要注意的是,这个序列实际上是整数序列,而不是单纯索引col6得到的复合索引序列。
groupby方法
Series 和 DataFrame 都支持groupby方法
主要参数
- by 分组依据
支持的类型
与df或s等长的 序列
df中的某一列series
s本身
其他与df或者s 等长的 series
等长的 list,np.array等。
函数调用
函数的参数,是df或者s的index。
注意:index的不同类型
函数体:对index的值进行处理,返回不同的值。这个返回值是分组后的每个组的名字
如图所示
字典映射
原理:{0:'b', 1:'a', 2:'a', 3:'b',....}
这个字典的 key 是 index
value 是 分组值
然后对字典的value值进行分组
分成 a,b... group。
常用与column的分组
df.groupby({'film':'娱乐','game':'娱乐','book':'学习'},axis=1)
语法糖
如果参与分组的是df 本身,可以直接使用列名或者index名,df.groupby(列名) 等价于 df.groupby(df[列名])
- axis 沿着那个方向拆分
axis = 0 沿着行的方向,根据by得到分组依据对df 在行的方向上依据分组依据的index,进行分组。
axis = 1 沿着列的方向。注意,此时by传递的分组依据要和df.columns 等长。
- level 如果df是复合索引,且by 是df本身,可以用level指定聚合分组的层级。
子主题
- as_index 是否显示分组的index
默认显示 True
index = 分组后每个组的名称
index值实际上是by对应序列2所有元素去重后的序列。
index的值 = 序列2 去重后的值。
根据groupby 算法,序列2,df['gender']所有元素去重后的得到元素['female','male'],
然后分别计算female和male 在序列2中的所有位置并返回
female-->所有female在序列2中的位置【index序列】。
male --> 所有male在序列2中的位置【index序列】
然后分别用这两个序列,分别索引序列1,就得到了序列1的分组结果两个 DataFrame。
可以通过 get_group('female') 或者 get_group('male') 分别查看两个DataFrame。
然后分别计算female和male 在序列2中的所有位置并返回
female-->所有female在序列2中的位置【index序列】。
male --> 所有male在序列2中的位置【index序列】
然后分别用这两个序列,分别索引序列1,就得到了序列1的分组结果两个 DataFrame。
可以通过 get_group('female') 或者 get_group('male') 分别查看两个DataFrame。
可以对照此思路将两个分组DataFrame合并到一个表中
点击查看
子主题
这个index 主要用于聚合计算后的结果的索引。
如图 点击查看
子主题
如果是 False
聚合计算的结果,不使用以上的索引,直接用数字索引
如图,点击查看
子主题
- sort groupkeys是否自动排序
如果为True【默认】
将会对序列2去重后的结果自动排序
对户主身份证字段去重后会自动排序。顺序是根据 户主身份证字符的顺序
如果为False
不自动排序,仍然使用原来序列2的顺序。
依然按照原序列的顺序。由于少了一步排序操作,对于大数据量的计算,设为False,可以减少计算量。
- group_keys 在如果调用apply()方法,且返回的是其他独立结返回值,group_keys是否作作为结果索引的一部分。
True
同 pd.concat()的keys 参数,如果为true ,keys = groups.keys()
分组的name 会作为apply函数返回结果的索引的一部分。
False
gourps.keys() 不作为索引的一部分
关于apply函数的原理
例子:
子主题
apply 函数作用于所有分组的数据【dataframe 或 series】
apply 函数返回值没有太多限制,
但是如果是DateFrame、series,则要注意这些返回的DataFrame/series 在执行最后concat操作时shape是否能对齐。
如果不能对齐,则会抛出异常。如果能对齐,并执行concat操作的话,group_keys 的作用等同于pd.concat函数的keys参数。
结果与 df.groupby(['gender','education'])结果一样,但算法却不一样。
- squeeze 如果遇到一维dataframe 自动转换成1维series
默认为 False
True
返回GroupBy对象
GroupBy 对象的属性和方法
groups
以字典形式返回所有分组索引。
格式 : {'分组名1':分组索引1,'分组名2':分组索引2,……}
[] 或者 .
如果是DataFrameGroupBY对象 使用[]或者. 就可以返回某一列的groupby对象。
没有使用[]或者.
会返回所有列的groupby对象。
子主题
使用[]或者.
返回某一列的groupby对象
返回1列
返回多列
get_group('female')
返回多个分组中的某个,如果是序列1是DataFrame,那么会返回female所索引的 DataFrame
子主题
计算 apply
aggregate() 和 agg() 聚合函数
作用通过agg调用函数进行运算
例子
子主题
聚合函数运算的原理
当调用agg聚合函数时,agg的作用是对分组后各个组的DataFrame按照 行的方向进行聚合运算【一列相加、求平均等】
子主题
然后把不同分组的运算结果聚合成一个DataFrame输出。
再将不同group的DataFrame预算结果,拼合成一个DataFrame
聚合函数调用各种方式
简单调用
不使用agg连接符,直接使用的聚合函数
文档中直接使用的聚合函数
如图
各种统计聚合计算
标准差
df.groupby('gender').std()
子主题
计算标准误差
df.groupby('gender').sem()
describe() 各类计算描述
df.groupby(['education','family']).describe()
快速运算
first() 和 last() nth()
返回每个组DataFrame的第一行和最后一行
nth 返回指定的行
子主题
size()
返回每个组中DataFrame或者Series的行数。
子主题
注意和count的区别
子主题
使用aggregate 或者 agg 聚合函数
df.groupby('gender').agg(np.sum)
df.groupby(['education','family']).agg(np.sum)
多个聚合函数的调用
df.groupby('gender').agg([np.sum,np.std,np.mean])
注意:多个函数调用后,尽量选择具体列进行多个聚合函数的运算,不然每一列都会被拆分成sum/std/mean 到时结果的列会很多。
例子
每个列下面是函数名,如果是自定义的匿名函数,则显示lambda。
其实也可以用pivot_table。实现原理不同,但是结果相同
自定义运算结果的列的名称
通过 rename columns的方法
子主题
创建 NamedAgg 对象
例子
子主题
新列名 = pd.NameAgg(column=参与聚合的列名,aggfunc=聚合函数)
注意:通过创建NameAge对象,一次只能对一列,使用一个聚合函数进行计算。
好处:可以对不同列 采用不同的聚合函数,增加聚合计算的自由度
虽然不能使用 agg([np.sum,np.max,np.mean]) 每一列计算三次,
但是通过该方法,可以更加自由的定义自己要处理的列和聚合函数。
但是通过该方法,可以更加自由的定义自己要处理的列和聚合函数。
简单的调用方式
新列名 = (参与聚合的列名,聚合函数)
如果想自定义带有空格的列名 如 film sum。要么定义film_sum,也可以使用agg(**{})的方式
df.groupby('gender).agg(**{'film sum':np.sum})
子主题
关于函数装包和解包的技巧
如果函数定义中有 **kwargs
子主题
技巧
在调用函数时可以直接使用 pack(**{'key':'value'}),最终这个字典也会被装包。
子主题
如果 序列1是series,那么 agg 函数调用方式 新列名 = (参与聚合的列名,聚合函数)
可以简化为:新列名 = 聚合函数
df['book'].groupby(df['gender']).agg(总数=np.sum,平均数=np.mean,标准差=np.std)
子主题
不同列使用不同聚合计算的方法
第一种【推荐】 : 新列名 = ('df中的列名',聚合函数)
注意:如果直接输入 film = np.sum 则会抛出异常。
结论:如果传入聚合参数的形式是 key = value的话,则一定要传入 new_name = (col_name,aggfuc) 的形式
第二种:{'df中的列名':聚合函数}
注意在字典中 直接使用 {'new_film':('film':np.sum)} 是错误的!
会抛出异常。结论,正常情况下,如果传入聚合函数的形式是 {'key':aggfunc},则一定要传入 {'存在的列名':聚合函数} 的形式
且这种方式,并没有对输入的列重命名,所以看不出每一列的算法信息。如果需要对不同列采用不同聚合函数,建议用第一种方式。
且这种方式,并没有对输入的列重命名,所以看不出每一列的算法信息。如果需要对不同列采用不同聚合函数,建议用第一种方式。
因为如果直接传入的参数是字典,agg则默认将字典解析为key=列名,value=聚合函数
这种方式,显示的原来的列名,没有自定义新列名,看不出算法信息,所以不推荐!
transfrom()函数
算法分解:
先分组计算
比如 根据 gender 分组计算序列1每个列的最大值。
在将上面的结果,分别广播至各自的分组
如:female的聚合运算结果,会被广播赋值到 female组中DataFrame的每一行。
会得到这样的结果。
transform计算和agg计算的比较
agg 计算
得到的是每个分组的计算值。
transform 计算
每个分组聚合计算 --> 将计算结果广播赋值至对应分组的Series 或者 DataFrame --> 将所有分组的Series或Dataframe。combine 组合 --> 输出结果。
再将各个广播赋值后的组拼接输出。
输出的结果中:原dataframe 的索引所有没有改变,但是值都被各个组的聚合函数运算结果所覆盖。
注意的问题:
如果自定义聚合函数时需要注意,因为transform会有个广播赋值的过程,
如果自定义聚合函数有inplace属性是一定要设为inplace=False
否则会有不可预知的结果
如果自定义聚合函数有inplace属性是一定要设为inplace=False
否则会有不可预知的结果
transform函数的一些常用应用场景
例子 1 分组填充
给每个分组分配一个编号。
先根据户主身份进行分组,用自定义函数对每个分组进行聚合运算后,再所有分组合并返回。
例子2 NaN数据的科学处理
要求:对于每个分组的NaN数据,如果简单的用fillna(s.mean)方法,会用整体series的平均值来填充每个分组的NaN。
这样会造成数据的不准确,此时可以使用transform,对每个分组的NaN数据,依据这个分组的平均值填充,
这样NaN 数据的填充会准确一些。
这样会造成数据的不准确,此时可以使用transform,对每个分组的NaN数据,依据这个分组的平均值填充,
这样NaN 数据的填充会准确一些。
可以看出,使用transform分组填充NAN,和直接填充NAN数据的差异。
除了transform ,ffill(),bfill()等填充函数,算法与transform 一样
注意一点:和transform一样,类似的分组计算并组合【combine】输出,在GroupBy对象中选择某个具体的列,再赋值给一个新列,会好一些。
如果处理多列,transform运行的结果,并没有重命名,有可能会引起列名重复,引起困惑。
窗口滚动函数
rolling 方法
rolling函数的算法原理
例如:当 s.rolling(4) 时,会生成如右图的方框,每个方框有4个元素。
子主题
依次移动方框,直至series底部,最后返回 rolling 对象。
返回的rolling对象中每个元素,是每个方框所框的元素
可以用一个简单的函数,查看rolling对象的每个元素。
如果指定了聚合函数,聚合函数将会对每个方框中的序列进行聚合计算。
前面三个元素,无法组成方框,所以不计算。直到第四个元素,才能组成方框,所以索引是从第4个元素 开始的。
将运算的结果赋值给对应方框的位置
第一个rolling 元素的index 为 3。所以赋值从3开始。
groupby 对象 可以使用 rolling方法
如果使用gropby,rolling函数会分别对每个分组进行滚动运算,
和transform一样,最终也会将各个分组的运算结果进行组合combine输出。
和transform一样,最终也会将各个分组的运算结果进行组合combine输出。
结果
expanding 方法
算法原理
示意图
如图所示,如rolling不同,expanding 的方框是随着方框的移动逐渐变大。
当方框移动到序列底部时,返回expanding 对象。
对象的每个元素是,原series所在位置元素向上的所有元素。
示意图
子主题
最后可以调用聚合函数,对eapanding对象中每个元素进行聚合运算,结果如上示意图
同理,groupby对象也可以使用 epanding函数
原理同rolling
先分组计算,执行expanding 的聚合运算,再combine组合输出。
resample() 方法
算法原理
根据指定的时间间隔分割序列,返回resampler 实例。
例子
会根据resample中指定的间隔,移动方框。resampler实例中每个元素是方框中数据块。
resampler 实例的每个元素是方框所框柱的数据块
可以指定不同的聚合函数对方框中的数据块进行计算
对每个数据块进行计算,返回计算结果。
注意事项 : 该方法只对索引为时间类型的Series或者DataFrame有效。
如果index 为非时间类型的序列,则会抛出异常。即使series的类型为datetime也会抛出异常。
resampler 对象也可以使用transform 方法
d_t.resample('4D').transform(np.sum)
filter() 分组过滤
算法原理
通过自定义过滤函数,指定分组运算的判断条件。
如果该组符合条件,返回改组所有数据。注意:判定的是一个组,而不是组里的元素
filter 函数作用对象是组。而不是组里的元素。
如果该组不符合条件,返回NaN,可以通过filter中 dropna参数来控制NaN 是否显示。
自由度更高的 apply 方法
与agg 和transform的区别
agg 和 transform 的自定义函数返回值有限制,必须是1维的seires,或者单个数值,不能是dataframe
例子
自定义函数的返回值必须要符合聚合运算广播规则。否则会抛出异常。
apply 对函数返回的shape、类型无限制
apply方法,没有聚合运算,直接返回apply自定义函数的运算结果,所以不受广播规则限制,原则上可以是任何返回值。
例如:返回sku 各个分组数据中name列中 含有llc 的数据。
用agg会抛出异常,但是用apply 并不会
pipe 方法
groupby的一些特性
1、NaN 是不能被分组的。
gender列有NaN 数据,但是groupby并没有将其分组。
2、如果对category 进行分组,默认显示全部categories 即使有些没有出现在series中。
子主题
但是groupby如果指定 observe=True 则没有显示的categories 将不会显现。
子主题
关注 qcut 方法
3、可以通过实例化pd.Grouper对象,创建自定义的分组规则。
4、返回各个分组的指定行
df.groupby('gender').head()
返回每一组的
df.groupby('gender').last()
倒数第几行
df.groupby('gender').nth()
指定行
5.枚举方法
与enumerate 函数类似,返回序列中各个元素的索引值。
pandas中的枚举函数为
cumcount()
time序列
pandas 提供4种关于时间序列的概念
Timestamp类
与datetime库中的datetime类型
Timedelta 类
与datetime库中的timedeltal类相似
Period 类
Dateoffset 类
时间偏移量
pd提供的创建时间序列的函数
文档
子主题
to_datetime()
主要参数
arg
datetime实例
时间字符串
整数
默认为时间戳
子主题
以及上面元素构成的元组、列表、序列等。
Dataframe和字典
如图
errors
ignore
忽略,不符合要求的元素被忽略
coerce
解析成NaT
raise
抛出异常
dayfirsrt 和 yearfirst
12/11/10
dayfirst = True
2010-11-12
有些地区的时期格式是月日年。
dayfirst = False
2012-11-10
默认
yearfirst = True
2012-11-10
如果两个都是True,yearfirst优先。
如果特殊情况,尽量不要设为True
utc
增加时区
box
True
返回类型为datatime的序列或者index
False
返回array
新版本将弃用,可以使用to_numpy(),to_datetime64() 返回numpy时间类型的数组
format
自定义时间字符串的解析方式
例子
有些时间字符串无法解析时,可用format参数。类似datetime.strptime方法。
exact
True
精确匹配时间字符串格式
默认
False
子主题
unit
时间单位
D,s,ms,us,ns
origin
初始时间设置
unix
1970-1-1
julian
公元前4713年1月1日
自定义:传入Timestamp对象
默认 unix 和 自定义当前时间为初始计时时间。
infer_datetime_format
False
Ture
解析时间格式
cache
True
缓存,如果是时间字符串,通过设置缓存,可以提高解析速度。
原理:相同时间字符串的解析会被保存,无需再次解析
to_timedelta()
主要参数
arg
传入字符串
timedelta实例
以上的序列
unit 单位
常见单位
例子
子主题
errors
错误处理
raise 、ignore、coerce
date_range()
主要参数
start和end
开始和结束时间
periods
返回开始和结束时间,多少个时间间隔。
子主题
freq
时间偏移量
“5D”
五天的偏移量。从2020-03-03开始,每5天返回一个时间数据。
查看常见的 时间偏移字符串
子主题
normalize
标准化时间,没啥用
name
返回时间序列的名称
closed
返回的时间序列是否包含 start 、end
None
都包含
left
包含左边
right
包含右边
tz
时区信息
bdate_range()
period_range()
主要参数
start
datetime对象,时间字符串、Peirod对象,period字符串
如:pd.period_range('2020Q1','2020Q2',freq='D')
2020年第一季度和2020年第二季度,以天为单位返回时间序列
end
同上
periods
序列数量
注意start/end/periods 三个参数中2个必须明确指定
pd.period_range('2020Q1','2022Q1',periods=10) 会抛出异常。
date_range则不会,date_range会计算开始和结束的时间差,然后根据periods进行等分。
freq
时间频率
name
序列名称
返回固定时间周期period_index序列。
timedelta_range()
返回timedelta时间序列,即与sart相差多少天,多少小时……
主要参数
start
timedelta字符串
1day
5hours
timedelta对象
timedelta(days=1)
end
同上
periods
返回序列的数量
freq
时间频率
name
序列名称
closed
如果有start和end,决定是否包含start和end
infer_freq()
根据datetimeindex或者periodindex序列推断时间频率,并返回相应的freq字符串。
例子
子主题
在创建新的date_range或者period_range就可以用自定义的freq参数了
子主题
不同类型时间序列的比较
示例
子主题
timestamp和time span的区别
Timestamp对象和Period对象的区别
在时间的表现上,两个并无太多差异
timestamp只关注时间数据本身
可以增加时区,转换时区等多种操作。
period 关注的是时间序列,突出时间序列的规则性
创建timestamp对象和序列
timestamp对象的形式
使用Timestamp可以将 datetime实例、时间字符串、时间戳等实例化成timestamp对象。
用datetime实例创建 timestamp实例
子主题
用时间戳创建timestamp对象
unti时间单位,当输入的是时间戳,unti定义输出的数字是秒、分钟、还是毫秒……
子主题
用字符串创建timestamp对象
子主题
根据year/mont/day/hour/minute/second创建tiemstamp对象
同datetime创建。
timestamp对象本质上就是pandas的datetime对象,它所支持的属性方法与datetime基本类似
DatetimeIndex序列和datetime序列的构成元素是timestamp对象。
使用pd.to_datetime()创建DatetimeIndex序列
datetimeIndex的元素是timestamp对象。如果to_datetime('2020-03-04')只传入了一个时间,返回的也是tiamstamp对象。
datetime类型的Series中单个元素也是timestamp对象
子主题
创建timestamp序列的两个主要方法
pd.to_datetime()
返回 DatetimeIndex的情景
传入的参数是非Series 序列,都会返回DatetimeIndex
包括时间字符串、datetime实例列表、时间戳列表、numpy数组等。
子主题
返回datetimeSeries的情景
传入的是一个series序列,那么会返回datetime类型的序列
一个例子
读取带有timestamp列的数据,在读取时如果没有专门指定,会得到int类型序列
读取的series 数据类型为int
通过,pd.to_datetime(t,unit='s') 可以序列转换为datetime序列
子主题
反过来,使用pd.Series()且传递DatetimeIndex序列,也会得到datetime序列
子主题
pd.to_datetime的一些小技巧
format参数
场景:中文日期的处理
不使用format参数,会抛出异常
子主题
定义了format参数,正常读取
子主题
分列时间数据的读取
场景:年月日在不同列
要求:列名必须是year/month/day, 三列结合字符串必须符合%Y%m%d【20201202】。 如果1961是61就无法解析。
无效时间字符串的处理
通过errors指定
ignore
不解析
raise
报错
coerce
强制转换为NaT
时间戳的问题
有些数据库时间数据的格式是时间戳
例如
数据会呈现这样的格式。
读取时间戳,要注意几点
指定时间戳单位 unit
如果不指定,可能会导致读取时间错误,pandas默认的时间戳单位为ns纳秒
没有指定时间戳单位,读取成1970年
子主题
指定起始计算日期 origin
默认的是origin 是‘unix’,即1970-1-1 0:0:0至今的时间间隔。时间戳的概念也是这么来的
如果想自定义起始时间,可以给origin设定一个特定时间。
如果改变了origin,最终时间也会发生改变。
origin的常见参数
子主题
pd.date_range()
创建DatetimeIndex实例,除了使用to_datetime函数创建外,也可以使用date_range 和 bdate_range函数创建
有点类似range/arange函数。date_range
start、end
开始和结束时间
freq
定义步长
常见的有
"D"/"2D"
一天、两天……
"W"周
"M"月末
MS 月初
SM 半月
Q 季度末
QS 季初
A / Y 年末
YS AS 年初
H 小时
BH 工作小时朝九晚四
T min 分钟
S 秒
periods
定义数量
衍生函数 bdate_range() 工作日
timestamp序列的局限
pandas的时间戳使用纳秒作为基本时间单位
最多能储存584年。
每个timestamp占用64位空间,数据量大的话占空间。
更节省空间的做法,使用period对象存储时间数据
DatetimeIndex 的索引
对于index为DatetimIndex的DataFrame和Series的索引
支持的方式
整数索引
支持[start:end:step]的整数索引方式
步长改变 freq也会改变。
时间字符串的索引【支持切片】
df_t['2020-03']
索引3月份数据
df_t['2020-03-04':'2020-03-09']
索引2020年3月4日至2020年3月9日的数据
子主题
dt_s['2020-03-20':'2020-04']
索引3月20日至4月30日的数据。时间索引在切片时,只有部分时间字符串也可以正常索引
子主题
注意1:时间字符串的切片,以月为例:start-->每月1号00:00:00 开始。end-->每个月最后一天,如30号23:59:59
注意2 :切片的范围,受到freq的影响。
dt_index = pd.date_range("2020-3-4",periods=150,freq="H")
freq是小时。截止的时间是 23:00:00
如果freq是S
截止时间是 23:59:59 如果是2S,截止是23:59:58
DatetimeIndex对象的resolution属性,返回时间精度
子主题
根据时间索引的精度,freq=2S的索引结果。
datetime对象切片
df_t[datetime(2020,3,4):datetime(2020,3,5)]
如果是datetime对象的切片,会被认为是精确时间。
和字符串不一样,datetime(2020,3,5) 被认为是精确时间,达到 2020-3-5 00:00:00 就停止了。
dt_s["2020-03-04":"2020-03-05"] 字符串不是精确时间,所以会索引到 2020-03-05 23:59:59
truncate 方法
truncate 索引类似datetime
子主题
fancy索引
DataFrame索引时注意
dataframe使用[] 仅支持匹配模式的切片索引,不支持精确索引。
使用精确索引的模式,会抛出异常。
字符串索引的精度与索引的精度一致,也会引发异常
小于索引精度就不会引发异常。
如果索引的时间精度与index时间精度一致,则可以使用loc索引。
这样不会抛出异常。不过不建议这么做,索引效率很低
点击查看三种索引方式的时间
使用loc索引时,如果是精确索引,建议使用datetime对象索引,效率会更高。字符串索引效率太低!
DataFrame如果直接用 loc索引时,
如果使用花式索引的方法,那么传递的索引值必须是精确的。
使用时间字符串匹配来花式索引,会引发异常。
如果使用花式索引的方法,那么传递的索引值必须是精确的。
使用时间字符串匹配来花式索引,会引发异常。
会报错!
即使字符串时间精度与index时间精度一致,也会报错
解决方法:使用精确的datetime对象,或者timestamp对象进行花式索引。
使用datetime和timestamp对象,都可以进行花式索引。对于缺失的时间部分,构造函数会用0补齐。
DateOffset对象【时间偏移】
通过实例化dateoffset对象可以自定义时间偏移量。如果使用date_range函数,dateoffset对象可以作为freq参数
子主题
DatetimeIndex 和 datetime Series 可以使用dateoffset对象,算法上类似apply,可以作用到序列每个元素
datetime Series的计算
子主题
DatetimeIndex的计算
子主题
多样化的时间间隔设置
如 BusinessDay
算法原理:从start时间开始,按照天为单位累加,并判断当前日期的weekday属性,如果是周六、周日、跳过,
继续累加 判断 符合条件跳过 以此类推
继续累加 判断 符合条件跳过 以此类推
也可以自定义假期、周末日期 CustomBusinessDay
定义了暑假的时间序列。算法同BusinessDay。遇到定义的时间规则,跳过。
pandas内置了许多国家的假期规则。如 USFedralHolidayCalendar
思考:中国周日补周五班,该如何设置?
自定义上下班时间 BusinessHour
以小时为单位递增,如果时间在定义的时间段外,跳过。
定义晚班
将工作日、休息日、假期、上下班时间 CustomBusinessHour
定义了假期的BusinessHour。假期内的Hour 将会被跳过。
pandas内置的DateOffset子类,并且制定了别称,使用时直接用别称就可以了
直接使用 如 freq='D'
也可以组合使用:freq="14D12H"
14天12小时
嵌套使用 如:W-SUN
每周周日
关注一下 DateOffset类及其子类中两个参数
normalize
True
根据精度调整时间的显示方式
False
完整显示时间。
实际上的算法是:False 根据当前时间累加。Ture 根据当前时间的当天的00:00:00累加。
体会不同
n
时间间隔的倍数
这两个是等价的
与DateOffset相关的方法
shift方法
如果不是时间index序列,shift的作用是整体移动数据
数据整体向下移动了3行。
如果是时间index序列
没有freq参数,效果同非时间序列。
数据整体下移3行
tshift() 方法
如果使用tshift方法,tshift(3),默认修改DatetimeIndex。如果没有freq,默认为D。
tshift和shift的主要区别。
如果有freq参数,则作用于时间index
使用 periods 和 freq的结果。
使用freq参数,作用于时间index。如果是periods 和 freq 一起用,时间index的偏移量是 periords * freq
freq参数仅作用于datetimeIndex的Series或者DataFrame,时间类型的Series不支持。
asfreq 方法
修改DatetimeIndex的时间间隔
参数
freq
修改后的freq
method
如果修改后的freq精度要高于原DatetimeIndex的精度时
原来的freq 为 ‘D’改为“H”后,会产生NaN空值。method 指定空值的填充方式
backfill’/’bfill’ 后向填充
‘pad’/’ffill’ 前向填充
检查如果某个非空值后面是否有NaN,如果有,则填充这个值,继续判断,如果是,继续填充这个值,直至下一个非控值。
fill_value
自定义填充
nomalize
同data_range的nomalize,是否增加至午夜零点
how
和periodindex有关
resample 重取样
时间序列的重取样
Downsample 下取样
取样时间精度小于时间索引的精度。如:取样精度为分,时间索引精度为秒
返回:类似groupby对象,并支持groupby对象的方法。
多个数据 折叠成 一个数据,可以使用agg聚类函数,进行计算。groupby的属性方法都支持。
Upsample 上取样
取样的时间精度高于时间索引的精度。如:取样精度为秒,时间索引精度为分。
取样精度秒 高于索引精度 分
返回:一个展开的数据 例如 分钟 会展开 01-59 秒的数据,数据默认填充为NaN
此时,groupby的一些聚合操作没有意义,关注的是对NaN数据的填充
上采样 支持的NaN填充方法
ffill 前置填充
NaN 前的值填充
bfill 后置填充
NaN 后的值填充
interpolate 插值填充
支持的method
子主题
resample方法
resample的主要参数
rule 时间规则
freq 字符串
子主题
timedelta对象
使用timedelta对象
Dateoffset 对象
子主题
axis 聚合轴
closed 开闭原则
默认为None
left 和 right
如果是下采样,确定下采样的边界。
下采样的原理:根据freq和DatetimeIndex第一个时间值,确定采用的边界。
例如:以freq为5Min为例,则根据时间中的分钟 00:00:00 来确定采样间隔。
会返回,00:00:00-00:05:00……00:55:00-00:00:00 的间隔序列。
根据closed的设定值,确定DatetimeIndex中的时间值,属于哪个间隔序列。
如果closed 设置为right,00:00:00 会作为终点。
如果初始时间为00:00:00 的话,那么这个时间值会落入:23:55:00-00:00:00 区间
如果初始时间为00:00:01的话,那么这个时间值会落入:00:00:00-00:05:00 区间。
如果初始时间为23:59:59 的话,那么这件时间值会落入: 23:55:00-00:00:00 区间
例如:以freq为5Min为例,则根据时间中的分钟 00:00:00 来确定采样间隔。
会返回,00:00:00-00:05:00……00:55:00-00:00:00 的间隔序列。
根据closed的设定值,确定DatetimeIndex中的时间值,属于哪个间隔序列。
如果closed 设置为right,00:00:00 会作为终点。
如果初始时间为00:00:00 的话,那么这个时间值会落入:23:55:00-00:00:00 区间
如果初始时间为00:00:01的话,那么这个时间值会落入:00:00:00-00:05:00 区间。
如果初始时间为23:59:59 的话,那么这件时间值会落入: 23:55:00-00:00:00 区间
如果closed设置为left,那么 00:00:00 会作为起点:
00:00:00 会落入 00:00:00-00:05:00 的区间
00:00:01 会落入 00:00:00-00:05:00 的区间
23:59:59 会落入 23:55:00-00:00:00 的区间
00:00:00 会落入 00:00:00-00:05:00 的区间
00:00:01 会落入 00:00:00-00:05:00 的区间
23:59:59 会落入 23:55:00-00:00:00 的区间
需要注意的问题:如果freq是M【月末】A【年末】Q【季末】BM/BA/BQ【商业月末/年末/季末】W【周末】时
下边界必须是默认值,right。
如果是left,会发生采样错误。
解决的思路:可以将resample的rule 改为 "MS" 月初。
label 分组后的组名
left / right
根据resample的算法:resample会根据时间序列的初始值和freq生成相应的间隔序列。然后判断时间索引序列中某个时间值属于哪个间隔片段。
并依此进行数据分组。
并依此进行数据分组。
下取样的思路
注意一个问题:如果freq是M【月末】A【年末】Q【季末】BM/BA/BQ【商业月末/年末/季末】W【周末】时,label必须为right。
不然也会发生语义错误。
不然也会发生语义错误。
label的作用是取样后,指定各个【依据freq间隔序列生成】分组的名称。
left 用该间隔序列的左边界的值作为分组名
例子
right 用该间隔序列的右边界的值作为分组名
例子
注意事项:如果closed=‘right’,label最好也设置成 ‘right’
不然返回的结果容易引起混淆
None
默认
loffset
自定义label的偏移量
子主题
kind
timestamp
返回的是DatetimeIndex
period
返回的是PeriodIndex
convention
仅作用与periodIndex。
start
s
end
e
base
on
如果dateframe某一列是datetime类型。on,指定该列,进行分组。
level
如果是复合索引,指定复合索引的某一层。
这一层必须是时间序列
resample的聚合函数
与groupby类似
Period 对象
Period对象,反应的不是具体时间,而是一个时间段
创建Period对象
value
datetime实例
时间字符串
时间段的字符串
如 2020Q1
freq
Dateoffsset 子类实例
BusinessDay()
BusinessHour()
……
但有些子类不支持,如 MonthBegin()
DateOffset实例也不支持
freq 字符串
timedelta实例
timedelta实例
ordinal
时间戳
value值,ordinal不能同时赋值。
时间参数
year
month
day
quarter
hour
minute
second
period对象的算法特性
整数加减运算
period对象可以直接整数加减运算,整数增加或减少的freq。
Timestamp对象,如果有freq参数,可以算术运算,但是未来版本会删除
period对象间的运算
freq 相同的period对象可以进行减法运算
返回dateoffset 对象。
不同freq的period对象不可以进行减法运算
返回dateoffset 对象。
不同freq的period对象不可以进行减法运算
和datetime一样,不支持period对象间的加法运算
时间间隔对象间的运算
与timedelta对象间的运算
前提:timedelta中的参数,时间精度=freq的时间精度。如果timedelta精度更高,则必须是freq的整数倍。
r3的时间精度与r1 freq='D'的时间精度一样,可以进行运算
r4的时间精度是r1时间精度的整数倍 7D 也可以进行运算
r5不能预算,因为时间精度不是整数倍。r5 = timedelta(seconds=60*60*24) 这样就可以
这样计算方式,仅对天、小时、分钟、秒、毫秒的时间精度有效。
周、月、年、季度均不支持。
r3的时间精度与r1 freq='D'的时间精度一样,可以进行运算
r4的时间精度是r1时间精度的整数倍 7D 也可以进行运算
r5不能预算,因为时间精度不是整数倍。r5 = timedelta(seconds=60*60*24) 这样就可以
这样计算方式,仅对天、小时、分钟、秒、毫秒的时间精度有效。
周、月、年、季度均不支持。
与offset对象间的运算
周以上的时间 其dateoffset对象的精度与freq字符串代表的时间精度必须一致。
周以下的时间精度,dateoffset对象的精度与freq字符串代表的时间精度呈整数倍关系
周以下的时间精度,dateoffset对象的精度与freq字符串代表的时间精度呈整数倍关系
PeriodIndex对象
创建periodIndex的几种方式
直接使用PeriodIndex创建实例
与period_range不同,在实例化PeriodIndex对象时,传入的时间序列对规则性,没有要求。
使用period_range函数创建
主要参数
start
end
periods
注意:start,end,periods 参数,三个只能选两个
freq
name
支持的参数输入
时间字符串
pd.period_range('2020-01-01','2020-12-31',freq="D")
datetime/timestamp/Period对象
需要注意的是,Period对象要符合运算规则,即start和end的freq参数要一样。不一样会报错
如果start和end都是Period对象,且freq相同,则可以省去freq参数
如果 start是Period对象,end不是,则需要定义freq参数
如果 start是Period对象,end不是,则需要定义freq参数
如果start和end都是Period实例,那么period_range的freq参数与Period实例的freq参数 无需要求一致。
PeriodIndex的运算法则
同Period对象
会与PeriodIndex的所有元素进行运算
PeriodIndex的索引
切片索引同DatetimeIndex
子主题
注意1:使用花式索引,索引的值必须是Period对象。
使用字符串和datetime对象会抛出异常
注意2:如果PeriodIndex不是递增或者递减序列,部分字符串切片会抛出异常
datetimeIndex可以正常索引
无序且间隔不均匀的时间序列,虽然可以创建PeriodIndex,但是不可以进行切片索引,
部分时间索引也会抛出异常。
部分时间索引也会抛出异常。
如果通过PeriodIndex创建的有序序列,则可以正常索引。
修改Period和periodIndex的freq
asfreq方法
主要参数
freq
how
end/start 或者 s,e 简称
timespan 的 开始 or 结束
场景
修改后的freq精度高于原时间精度。例:原 “M”,改后 "D"
修改后时间精度高于修改前,
也就是说,修改后每个period的时间范围会拓展【2020-03,--> 2020-03-01—2020-03-31】,
此时可以用how 来指定修改后的period时间是开始的2020-03-01 还是 2020-03-31
默认为 e,end 选取 2020-03-31
也就是说,修改后每个period的时间范围会拓展【2020-03,--> 2020-03-01—2020-03-31】,
此时可以用how 来指定修改后的period时间是开始的2020-03-01 还是 2020-03-31
默认为 e,end 选取 2020-03-31
how 参数为 start 后,选取 时间区间的开始
修改后的freq精度,低于原时间精度。例原为 D 修改后 M
此时how参数无意义
super-period 超周期
如 A-NOV 表示一年结束的时间为11月
如果将 Period('2020-12',freq='M')的freq改为A-NOV时,那么 2020-12 会转换成 2021年
应用场景:不同的杂志、公司会有不同的季度定义。如果给定一年的时期,可以用asfreq判断具体时间属于哪个自定义季度。
通过asfreq("Q-AUG") 自定义当年的季度末为8月份,那么对于的季度会发生改变。
asfreq可以进行更加复杂的计算
子主题
PeriodIndex 和 datetimeIndex 的转换
to_period 方法
to_timestamp 方法
将periodindex 转换为 DatetimeIndex
有意思的变换,通过改变freq参数,可以转换时间值。一月为季度末。
算法原理:第一步:转换asfreq("Q-JAN")
一月份末为四季度末尾,那么 2020-1-1,实际上会变为2020年四季度‘2020Q4’
第二步:将period对象,转换为timestamp对象
2020Q4 ==》 2019-11,2019-12,2020-1
如果 to.timestamp没有指定how参数,则默认为s。即 2019-11
结果:
how = 's' 开始,所有的2020Q4 --> 2019-11-01 2021Q1 --> 2020-01-01
how = 'e',所有的2020Q4 --> 2020-1-31
2021Q1 --> 2020-04-30
Timedelta 对象
0 条评论
下一页