Python模块 —— Pandas
Pandas(三)—— 变形、连接
大家可以关注知乎或微信公众号的share16,我们也会同步更新此文章。
五、变形
什么是长表?什么是宽表?这个概念是对于某一个特征而言的。例如:一个表中把性别存储在某一个列中,那么它就是关于性别的长表;如果把性别作为列名,列中的元素是某一其他的相关特征数值,那么这个表是关于性别的宽表。
下面的两张表就分别是关于性别的长表和宽表:
显然这两张表从信息上是完全等价的,它们包含相同的身高统计数值,只是这些数值的呈现方式不同,而其呈现方式主要又与性别一列选择的布局模式有关,即到底是以 long 的状态存储还是以 wide 的状态存储。因此,pandas 针对此类长宽表的变形操作设计了一些有关的变形函数。
5.1 长表变宽表 —— pivot / pivot_table
数据透视表(pivot / pivot_table):
df.pivot(index,columns,values) 等价于 pd.pivot(df,index,columns,values)
- index/columns/values:列名,可用列表表示;
- 利用 pivot 进行变形操作需要满足唯一性的要求,即在新表中的行列索引对应唯一的 value ;若不满足唯一性,可以使用pivot_table;
df.pivot_table(values,index,columns,aggfunc,fill_value,margins,dropna,margins_name,observed,sort) 等价于 pd.pivot_table(df,values,index,columns,aggfunc,fill_value,margins,dropna,margins_name,observed,sort)
- index/columns/values:列名,可用列表表示;
- aggfunc:函数,可用列表表示;默认为mean;
- fill_value:默认None,用于替换缺失值的值;
- margins:默认False,添加行/列的小计/总计;
- dropna:默认True,是否删除全是缺失值的列;
- margins_name:默认All,小计/总计所在行/列的名称;
- observed:默认False;若为真,仅显示分类分组的观察值;反之显示分类分组的所有值;
- sort:默认True,指是否对结果进行排序;
5.2 宽表变长表 —— melt / wide_to_long
df.melt(id_vars,value_vars,var_name,value_name,col_level,ignore_index) 等价于 pd.melt(df,id_vars,value_vars,var_name,value_name,col_level,ignore_index)
- id_vars:不需要被转换的列名,在转换后作为标识符列(不是索引列),可用元组、列表、数组表示;
- value_vars:需要被转换的列名,若未指明,除id_vars之外的其他列都被转换,可用元组、列表、数组表示;
- var_name:设置由 value_vars 组成的新的列名,若为None,则使用variable;
- value_name:设置由 value_vars的数据 组成的新的列名,默认value;
- col_level:(int或str)若列是多重索引,则使用此级别进行融合;
- ignore_index :默认True,若为True,则忽略原始索引;若为 False,则保留原始索引;
pd.wide_to_long(df,stubnames,i,j,sep,suffix)
- stubnames:提取以指定字符串开头的列,并以指定字符串为新的列名,可用str、list表示;
- i:用作索引的列,可用str、list表示;
- j:新的一列命名,其数据用 含有stubnames的列名 的剩余部分填充;
- sep:分隔符,含有stubnames的列名,用什么分割;
- suffix:捕获正则表达式匹配的后缀,默认‘\d+’;
5.3 交换行列索引 —— stack / unstack
上篇文章中,我们学习到了索引内部的层交换,即 swaplevel 或 reorder_levels ;那么,行列索引之间的交换
,可用 stack 或 unstack。
df.stack(level, dropna)
- 将 列索引 转为 行索引
- level:默认-1,可用int、str或列表;
- dropna:是否删除缺失值,默认True;
df.unstack(level,fill_value)
- 将 行索引 转为 列索引
- level:默认-1,可用int、str或列表;
- fill_value:若产生缺失值,则用此值替换NaN,默认None;
5.4 其他变形函数 —— crosstab / explode / get_dummies
pd.crosstab(index,columns,values,rownames,colnames,aggfunc,margins,margins_name,dropna,normalize)
- 默认情况下,crosstab:用来计算因子的频率表
- index/columns:行/列中进行分组的值,可用列表、数组或Series表示;
- values:默认None,若有值,即根据因子对数据进行汇总,此时需要指定aggfunc;
- rownames/colnames:默认None,若可以,需命名相应数量的名字;
- margins:默认False,添加行/列的小计/总计;
- margins_name:默认All,小计/总计所在行/列的名称;
- dropna:默认True,是否删除全部为缺失值的列;
df.explode(column,ignore_index)
- 对某一列的元素进行纵向的展开,被展开的单元格必须存储 list, tuple, Series, np.ndarray 中的一种类型
- ignore_index:默认False,若为True,则生成的索引将标记为 0、1、…、n - 1;
df_ex = pd.DataFrame({'A': [[1, 2], 'my_str', {1, 2}, pd.Series([3, 4])], 'B': 1})
df_ex
df_ex.explode('A')
df_ex.explode('A', ignore_index=True)
pd.get_dummies(data,prefix,prefix_sep,dummy_na,columns,sparse,drop_first,dtype)
- get_dummies 是用于特征构建的重要函数之一,其作用是把类别特征转为指示变量(属于返回1,反之返回0)
- data:可为ndarray、Series、DataFrame
- prefix:str,get_dummies转换后列名的前缀,默认None
- dummy_na:默认False,若是 False ,忽略NaN,反之添加一列表示NaN;
- columns:指定需要实现类别转换的列,否则转换所有列;
- drop_first:默认False,是否通过删除第一级,从 k 个分类级别中取出 k-1 个虚拟变量;
5.5 美国非法药物数据集
现有一份关于美国非法药物的数据集点此下载,其中substancename、drugreports,分别代表:药物名称、报告数量;
问题:
1. 将数据转为下图的形式:
import pandas as pd
df = pd.read_csv('/Users/liuye/Desktop/Pandas数据集/05 美国非法药物.csv').sort_values(['year','state','county','substancename'],ignore_index=True)
a = df.pivot_table(values='drugreports', index=['state', 'county', 'substancename'], columns='year').reset_index().rename_axis(columns={'year':''})
df.pivot(index=['state', 'county', 'substancename'], columns='year', values='drugreports').reset_index().rename_axis(columns={'year':''})
# 解析:
# df.pivot_table(···)/df.pivot(···)的结果:行索引是state、county、substancename,列索引是year中的唯一值;
# reset_index():把索引变成普通列; rename_axis():索引重命名
2. 将第1问中的结果恢复为原表;
b = a.melt(id_vars=['state', 'county', 'substancename'], var_name='year',value_name='drugreports').dropna(subset='drugreports')
c = b[df.columns].sort_values(['year','state','county','substancename'], ignore_index=True).astype({'year':'int64', 'drugreports':'int64'})
df.equals(c)
# 解析:
# b语句:将宽表转化成长表,删除drugreports列的缺失值
# c语句:将b表按df的列排序
3. 按state分别统计每年的报告数量总和,其中state和year分别为列索引和行索引,要求分别使用 pivot_table 函数与 groupby+unstack 两种不同的策略实现,并体会它们之间的联系;
df.pivot_table(values='drugreports', index='year', columns='state', aggfunc='sum')
df.groupby(['year','state']).drugreports.sum().unstack(1)
六、连接
6.1 关系型连接 —— merge / join
把两张相关的表按照某一个或某一组键连接起来是一种常见操作,如:学生期末考试各个科目的成绩表按照姓名和班级连接成总的成绩表、对企业员工的各类信息表按照 员工ID号 进行连接汇总。由此可以看出,在关系型连接中,键是十分重要的,往往用on参数表示。另一个重要的要素是连接的形式。
在 pandas 中的关系型连接函数 merge(值连接) 和 join(索引连接) 中提供了 how 参数来代表连接形式,分为左连接left、右连接right、内连接inner、外连接outer。
df1.merge(df2,how,on,left_on,right_on,left_index,right_index,sort,suffixes,copy, indicator,validate) 等价于 pd.merge(df1,df2,how,on,left_on,right_on,left_index,right_index,sort,suffixes,copy, indicator,validate)
- how:默认inner(交集),类似于sql的join,还可取值left、right、outer(并集)、cross(笛卡尔积);
- on/left_on/right_on:默认None,若df1与df2要连接的列名一样,用on;反之,用left_on和right_on;
- left_index/right_index:默认False,用左边/右边的index作为连接键;若为多重索引,右侧/左侧的df中的连接键数必须与级别数相匹配;
- sort:默认False,合并后的数据按字典顺序对连接键进行排序;
- suffixes:默认(_x,_y),字符串组成的元组,当左右两表存在重复列名时,分别加上后缀用于区分;
- indicator:默认False,若为True,结果数据新加一个名为‘_merge’的列,其返回值为:对于仅在左边匹配成功时,取值为left_only;仅在右边匹配成功时,则为right_only;两边都匹配成功时,则为both;
- validate:默认None,若指定,则检查合并是否属于指定类型(1:1 —— 检查合并键在左右数据集中是否唯一值;1:m —— 检查合并键在左侧数据集中是否唯一值;m:1 —— 检查合并键在右侧数据集中是否唯一值);
df1.join(other,on,how,lsuffix,rsuffix,sort)
- other:DataFrame、Series或DF列表; how:默认left;
- lsuffix/rsuffix:左表/右表相同列索引的后缀;
6.2 方向连接 —— concat / append / assign
pd.concat(objs,axis,join,ignore_index,keys,levels,names,verify_integrity,sort,copy)
- objs:要合并的DataFrame或Series,以列表传入;
- axis:默认0,0 是行,1 是列;
- join:默认outer(并集),还可取值inner(交集);
- key:默认None,使用该序列构建层次化索引,且该索引值在最外层;
df.append(other,ignore_index,verify_integrity,sort)
- 把other追加到df1的行末
- other:DataFrame或Series对象,或这些对象的列表;
- 若原表是int类型的索引,那可使用 ignore_index=True 对新序列对应的索引自动标号,否则必须对 Series 指定 name 属性;
df.assign(new_name,other,···)
- 把other追加到df1的列末,并给其命名(new_name)
- other:DataFrame或Series对象,或这些对象的列表;
- 一般通过 df[new_name] = other 的形式,就可以等价地添加新列;然而,使用 [] 修改的缺点是它会直接在原表上进行修改,而 assign 返回的是一个临时副本;
6.3 类连接操作 —— compare / combine
df1.compare(df2,align_axis,keep_shape,keep_equal)
- 比较df1与df2,并显示差异;返回的结果中,self 是指df1,other 是指df2;
- keep_shape:默认False,若为真,则保留所有行和列;反之仅保留具有不同值的那些;
- keep_equal:默认False,若为真,则相等的值保持原样;反之相等的值显示为NaN;
df1.combine(df2,func,fill_value,overwrite)
- fill_value:默认None,用fill_value的值填充NaN;
- overwrite:默认True,若为True,self中不存在于other中的列将被NaN覆盖;
6.4 美国疫情数据集
现有美国4月12日至11月16日的疫情报表点此下载,请将NewYork 的Confirmed、Deaths、Recovered、Active合并为一张表,索引为按如下方法生成的日期字符串序列;
pd.date_range(start,end,periods,freq,tz,normalize,name,closed,inclusive,···)
:返回一个固定频率的DatetimeIndex
- start/end:生成日期的左/右边界;
- periods:要生成的周期数,即序列中元素的个数;
- freq:默认为D,还可是5H、3M等;
- tz:默认情况下,生成的DatetimeIndex为timezone-naive,还可为‘Asia/Hong_Kong’等;
- normalize:默认False,在生成日期范围之前将开始/结束日期标准化为午夜;
- name:默认None,DatetimeIndex的名称;
- inclusive:是否包含两个边界点(即start/end);默认both,取值还可为neither、left、right; closed:作用与inclusive类似;
''' 方法一 '''
import numpy as np
import pandas as pd
# 生成的日期字符串序列
a = [i.strftime('%m-%d-%Y') for i in pd.date_range('20200412', '20201116')]
url0 = '/xxx/06 美国疫情/'
# 创建一个全是NaN的数据集
df_new = pd.DataFrame(np.full((1,4), np.nan), columns=['Confirmed','Deaths','Recovered','Active'])
# for循环读取csv文件
for i in a:
url = url0 + i + '.csv'
df = pd.read_csv(url)
df_cut = df.query(" Province_State == 'New York' ")[['Confirmed','Deaths','Recovered','Active']]
df_new = pd.concat([df_new,df_cut],ignore_index=True)
# 对最终文件进行修改
df_new = df_new.dropna()
df_new.index = a
df_new
''' 方法二 '''
date = pd.date_range('20200412', '20201116').to_series()
date = date.dt.month.astype('string').str.zfill(2) +'-'+ date.dt.day.astype('string').str.zfill(2) +'-'+ '2020'
date = date.tolist()
L = []
for d in date:
df = pd.read_csv('/Users/liuye/Desktop/data/06 美国疫情/' + d + '.csv', index_col='Province_State')
data = df.loc['New York', ['Confirmed','Deaths','Recovered','Active']]
L.append(data.to_frame().T)
res = pd.concat(L)
res.index = date
res
谢谢大家 🌹