参考:https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch5.html#id2
一、长宽表的变形(元素和列索引的转换)
长表:把性别Gender存在列名中(long状态)
pd.DataFrame({'Gender':['F','F','M','M'], 'Height':[163, 160, 175, 180]})
Gender | Height | |
---|---|---|
0 | F | 163 |
1 | F | 160 |
2 | M | 175 |
3 | M | 180 |
宽表:如果把性别作为列名,列中的元素是某一其他的相关特征数值,那么这个表是关于性别的宽表(wide状态)
pd.DataFrame({'Height: F':[163, 160], 'Height: M':[175, 180]})
Height: F | Height: M | |
---|---|---|
0 | 163 | 175 |
1 | 160 | 180 |
变形函数:
分类 | 函数 | 例子 | 备注 |
---|---|---|---|
长表转宽表 | pivot | df.pivot(index='Name', columns='Subject', values='Grade') | 三个要素,分别是变形后的行索引、需要转到列索引的列,以及这些列和行索引对应的数值,它们分别对应了pivot 方法中的index, columns, values 参数。且行列组合需要唯一 |
pivot_multi = df.pivot(index = ['Class', 'Name'],columns = ['Subject','Examination'],values = ['Grade','rank']) | 参数可以传入列表,形成多级索引;仍然需要组合的唯一性 | ||
pivot_table | df.pivot_table(index = 'Name', columns = 'Subject', values = 'Grade', aggfunc = 'mean') |
aggfunc 参数可以使用的聚合函数,不需要组合的唯一性,因为如果相同会被聚合起来 |
|
df.pivot_table(index = 'Name', columns = 'Subject', values = 'Grade', aggfunc='mean', margins=True) |
pivot_table具有边际汇总的功能 | ||
宽表转长表 | melt | df_melted = df.melt(id_vars = ['Class', 'Name'], value_vars = ['Chinese', 'Math'], var_name = 'Subject', value_name = 'Grade') |
和pivot是互逆操作;只能合并一个元素 |
wide_to_long | pd.wide_to_long(df, stubnames=['Chinese', 'Math'], i = ['Class', 'Name'], j='Examination', sep='_', suffix='.+') |
可以合并一部分元素 |
【练一练】
在上面的边际汇总例子中,行或列的汇总为新表中行元素或者列元素的平均值,而总体的汇总为新表中四个元素的平均值。这种关系一定成立吗?若不成立,请给出一个例子来说明。
下面的代码会报错,size不能作为汇总函数
df.groupby(['Subject','Name']).size()
df.pivot_table(index = 'Name',
columns = 'Subject',
values = 'Grade',
aggfunc='size',
margins=True)
二、索引的变形(行列索引之间的交换)
函数 | 例子 | 备注 |
---|---|---|
unstack | df.unstack() | 行索引转为列索引 |
df.unstack([0,2]) | 主要参数是移动的层号,默认转化最内层,移动到列索引的最内层,同时支持同时转化多个层;必须保证 被转为列索引的行索引层 和 **被保留的行索引层 **构成的组合是唯一的,否则会报错 | |
stack | df.stack([1, 2]) | 把列索引的层压入行索引 |
三、其他变形函数
函数 | 例子 | 备注 |
---|---|---|
crosstab | pd.crosstab(index = df.School, columns = df.Transfer) | 不是一个值得推荐使用的函数;在默认状态下,可以统计元素组合出现的频数,即count 操作 |
pd.crosstab(index = df.School, columns = df.Transfer, values = [0]*df.shape[0], aggfunc = 'count') | 也可以做其他的聚合操作,values传入要进行agg计算的类似序列的数据 | |
explode | df_ex = pd.DataFrame({'A': [[1, 2], 'my_str', {1, 2}, pd.Series([3, 4])], 'B': 1}) df_ex.explode('A') |
对某一列的元素进行纵向的展开,被展开的单元格必须存储list, tuple, Series, np.ndarray 中的一种类型。 |
get_dummies | pd.get_dummies(df.Grade).head() | 把类别特征转为指示变量;传入series或dataframe |
练一练
前面提到了 crosstab
的性能劣于 pivot_table
,请选用多个聚合方法进行验证。
1分别对learn_pandas表进行pivot_table和crosstab的转换,计算count和mean:
import datetime
beg_time = datetime.datetime.now()
df.pivot_table(index = 'School',
columns = 'Transfer',
values = 'Name',
aggfunc = 'count')
end_time = datetime.datetime.now()
print ("df.pivot_table:count", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df.School, columns = df.Transfer, values = df.Height, aggfunc = 'count')
end_time = datetime.datetime.now()
print ("pd.crosstab:count", end_time - beg_time)
beg_time = datetime.datetime.now()
df.pivot_table(index = 'School',
columns = 'Transfer',
values = 'Name',
aggfunc = 'sum')
end_time = datetime.datetime.now()
print ("df.pivot_table:sum", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df.School, columns = df.Transfer, values = df.Height, aggfunc = 'sum')
end_time = datetime.datetime.now()
print ("pd.crosstab:sum", end_time - beg_time)
结果:似乎没有很慢?
'''
df.pivot_table:count 0:00:00.011967
pd.crosstab:count 0:00:00.009974
df.pivot_table:sum 0:00:00.013121
pd.crosstab:sum 0:00:00.010976
'''
2.是不是数据量不够大,换一个大点的表试试:(31569条数据house_info.xls)
import datetime
print("index数量",df2.groupby(['city','rooms']).ngroups)
beg_time = datetime.datetime.now()
df2 = pd.read_excel("../data/house_info.xls")
df2.pivot_table(index = 'city',
columns = 'rooms',
values = 'area',
aggfunc = 'count')
end_time = datetime.datetime.now()
print ("df.pivot_table:count", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df2.city, columns = df2.rooms, values = df2.area, aggfunc = 'count')
end_time = datetime.datetime.now()
print ("pd.crosstab:count", end_time - beg_time)
beg_time = datetime.datetime.now()
df2 = pd.read_excel("../data/house_info.xls")
df2['test'] = df2.index.values
df2.pivot_table(index = 'city',
columns = 'rooms',
values = 'test',
aggfunc = 'mean')
end_time = datetime.datetime.now()
print ("df.pivot_table:mean", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index = df2.city, columns = df2.rooms, values = df2.test, aggfunc = 'mean')
end_time = datetime.datetime.now()
print ("pd.crosstab:mean", end_time - beg_time)
结果:crosstab还是更快一点
index数量 4336
df.pivot_table:count 0:00:00.885630
pd.crosstab:count 0:00:00.017953
df.pivot_table:mean 0:00:00.568480
pd.crosstab:mean 0:00:00.013963
3.换成3万条纯数字的,index数量为669:
import datetime
from string import ascii_uppercase
data = np.random.randint(0, 26, size=(3000, 10))
cols = list(ascii_uppercase[:10])
np.random.seed(2)
df3 = pd.DataFrame(data, columns=cols)
print("index数量", df3.groupby(['A', 'B'])['C'].ngroups)
beg_time = datetime.datetime.now()
df3.groupby(['A', 'B'])['C'].count().unstack(fill_value=0)
end_time = datetime.datetime.now()
print ("groupby:count", end_time - beg_time)
beg_time = datetime.datetime.now()
df3.pivot_table(values='C', index='A', columns='B', aggfunc='count', fill_value=0)
end_time = datetime.datetime.now()
print ("pivot_table:count", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index=df3.A, columns=df3.B)
end_time = datetime.datetime.now()
print ("crosstab:count", end_time - beg_time)
结果:crosstab确实是最慢的
index数量 669
groupby:count 0:00:00.002988
pivot_table:count 0:00:00.011968
crosstab:count 0:00:00.015994
4.换成3万条纯字母的,index数量为676:
import datetime
from string import ascii_uppercase
data = np.random.choice(list(ascii_uppercase[:]), size=(30000, 10))
cols = list(ascii_uppercase[:10])
np.random.seed(2)
df3 = pd.DataFrame(data, columns=cols)
print("index数量",df3.groupby(['A', 'B'])['C'].ngroups)
beg_time = datetime.datetime.now()
df3.groupby(['A', 'B'])['C'].count().unstack(fill_value=0)
end_time = datetime.datetime.now()
print ("groupby:count", end_time - beg_time)
beg_time = datetime.datetime.now()
df3.pivot_table(values='C', index='A', columns='B', aggfunc='count', fill_value=0)
end_time = datetime.datetime.now()
print ("pivot_table:count", end_time - beg_time)
beg_time = datetime.datetime.now()
pd.crosstab(index=df3.A, columns=df3.B)
end_time = datetime.datetime.now()
print ("crosstab:count", end_time - beg_time)
结果:两者差不多
index数量 676
#crosstab慢于pivot_table
groupby:count 0:00:00.006970
pivot_table:count 0:00:00.013941
crosstab:count 0:00:00.016956
#测试多次的其他输出:crosstab快于pivot_table
index数量 676
groupby:count 0:00:00.016970
pivot_table:count 0:00:00.030903
crosstab:count 0:00:00.024946
结论:在index数量类似的情况下,crosstab处理字符串的速度和pivot_table差不多(甚至有的时候要快),而crosstab处理数字的速度始终比pivot_table慢,随着index数量的增长,差距越大
四、练习
Ex1:美国非法药物数据集
现有一份关于美国非法药物的数据集,其中 SubstanceName, DrugReports
分别指药物名称和报告数量:
In [63]: df = pd.read_csv('data/drugs.csv').sort_values([
....: 'State','COUNTY','SubstanceName'],ignore_index=True)
....:
In [64]: df.head(3)
Out[64]:
YYYY State COUNTY SubstanceName DrugReports
0 2011 KY ADAIR Buprenorphine 3
1 2012 KY ADAIR Buprenorphine 5
2 2013 KY ADAIR Buprenorphine 4
- 将数据转为如下的形式:
df2 = df.pivot_table(index=['State','COUNTY','SubstanceName'],columns=['YYYY'], values='DrugReports').reset_index().rename_axis(columns={'YYYY':''})
----------------------------
answer:
res = df.pivot(index=['State','COUNTY','SubstanceName'], columns='YYYY', values='DrugReports').reset_index().rename_axis(columns={'YYYY':''})
- 将第1问中的结果恢复为原表。
注意最后要排序
df2.melt(id_vars=['State','COUNTY','SubstanceName'],
value_vars=[2010,2011,2012,2013,2014,2015,2016,2017],
var_name='YYYY',
value_name='DrugReports')
answer:
res_melted = res.melt(id_vars = ['State','COUNTY','SubstanceName'],
value_vars = res.columns[-8:],
var_name = 'YYYY',
value_name = 'DrugReports').dropna(
subset=['DrugReports'])
res_melted = res_melted[df.columns].sort_values(['State','COUNTY','SubstanceName'],ignore_index=True).astype({'YYYY':'int64', 'DrugReports':'int64'})
res_melted.equals(df)
- 按
State
分别统计每年的报告数量总和,其中State, YYYY
分别为列索引和行索引,要求分别使用pivot_table
函数与groupby+unstack
两种不同的策略实现,并体会它们之间的联系。
df.pivot_table(values=['DrugReports'],index=['YYYY'],columns=['State'],aggfunc='sum')
df.groupby(['State','YYYY'])['DrugReports'].agg('sum').unstack([0])
answer:
res = df.groupby(['State', 'YYYY'])['DrugReports'].sum().to_frame().unstack(0).droplevel(0,axis=1)
Ex2:特殊的wide_to_long方法
从功能上看, melt
方法应当属于 wide_to_long
的一种特殊情况,即 stubnames
只有一类。请使用 wide_to_long
生成 melt
一节中的 df_melted
。(提示:对列名增加适当的前缀)
df = df.rename(columns={'Chinese':'Grade_Chinese','Math':'Grade_Math'})
pd.wide_to_long(df,
stubnames=['Grade'],
i=['Class','Name'],
j="Subject",
sep='_',
suffix='.+').reset_index().sort_values('Subject')