Pandas变形

ryluo 2020-06-14 01:29:22
Pandas系列

Datawhale组队学习pandas笔记

透视表

首先看一下数据形式:

一般状态下,数据在DataFrame会以压缩(staked)的状态存放,例如上面的Gender,F和M两个类别被叠在了一列中,pivot函数可以将其展开为新的一列, 从下面的结果可以看出如果用ID作为index的话展开会存在很多的缺失值,其实这些缺失值也是可以进行处理的,后面会提到。

pivot_table

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

但是由于pivot局限性比较大,所以实际使用时pivot_table使用比较多,下面使用pivot_table实现上述的展开过程. 由于pivot_table的功能比较多,比较灵活,所以速度相比于pivot慢一些。

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

pd.pivot_table()的几个重要参数

df: 输入的DataFrame数据

index: 生成新表的索引,可以直接使用原始数据中的索引

columns: 将哪些列进行展开,这里也可以是一个列表,其中包含多个列

values: 将表展开后,显示的值是什么,这里一般是我们关心的值。

aggfunc

从上面的pivot或者pivot_table的结果可以看出,其实将某些列进行展开之后,就相当于对展开的列进行了一个分组,类似于groupby. 所以这里pivot_table函数提供了aggfunc来进行聚合统计操作。

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

上图的结果相当于展示了不同学校的男生和女生的平均身高和升高总和,这也也可以使用groupby实现相同的效果,并且得到的结果与pivot_table是一致的。但是需要分组功能的话还是推荐是用groupby,因为这个工具更加的专业。

margin

有时候我们可能需要汇总一些数据,并且显示在“边界上”,可以形象的认为是表格中数据的部分汇总,但是不限于求和,看下面这个例子,在上面的piovot_table的基础上加上了margin=True。

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

行列值都可以为多级索引

当行列都是多级索引的时候看起来就会比较复杂

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

crosstab(交叉表)

melt方法

交叉表是一种特殊的透视表。可以用于分组统计,如统计关于街道和性别分组的频数可以用以下代码实现,也就是展示不同街道上不同的性别的数量

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

同样也可以使用groupby来实现

df.groupby(['Address', 'Gender']).size() # 获取组数和组容量

需要注意的是:交叉表目前还不支持多级分组

从上面显示的结果可以知道,交叉表也可以对数据进行分组,那么自然就会有对分组进行聚合的方法。values和aggfunc就是对数据进行聚合的方法,但是需要注意的是这两个参数需要同时出现。

# 使用np人为的构造一些数据进行分组后的统计
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=df['Height'],aggfunc='mean')
#默认参数等于如下方法:
#pd.crosstab(index=df['Address'],columns=df['Gender'],values=1,aggfunc='count')

使用groupby也可以轻松实现上述的结果

df.groupby(['Address', 'Gender'])['Height'].agg(['mean'])

crosstab除了有margin的功能还有normalize(归一化)的功能

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

melt变形

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

首先将数据变成pivot的数据

df = df[['ID','Gender','Math']]

pivoted = df.pivot(index='ID',columns='Gender',values='Math')
pivoted.reset_index()
pivoted.reset_index().melt(id_vars=['ID'],value_vars=['F','M'],value_name='Math')
'''
id_vars:
value_vars:
value_name:
可以发现中间有许多的缺失值,所以最后需要将缺失值给删除了
'''
pivoted.reset_index().melt(id_vars=['ID'],value_vars=['F','M'],value_name='Math').dropna().set_index('ID').sort_index()
result.equals(df.set_index('ID'))
# 最终将变换之后的表有变换回来了

'''
True
'''

哑变量与因子化

Dummy Variable

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

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

将Class和Gender进行onehot编码

pd.get_dummies(df_d[['Class', 'Gender']]).head()

在上面的基础上添加Weight特征

pd.get_dummies(df_d[['Class', 'Gender']]).join(df_d['Weight']).head()

factorize

factorize自然数编码

pd.factorize(['a', 'd', 'c', None], sort=True) # sort是排序后再进行编码

'''
(array([ 0,  2,  1, -1], dtype=int64), array(['a', 'c', 'd'], dtype=object))
'''

pd.factorize(df_d['Class'], sort=True)
'''
array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1,
        1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3], dtype=int64),
 Index(['C_1', 'C_2', 'C_3', 'C_4'], dtype='object'))
'''

问题与练习

问题

【问题一】 上面提到了许多变形函数,如melt/crosstab/pivot/pivot_table/stack/unstack函数,请总结它们各自的使用特点。

【问题二】 变形函数和多级索引是什么关系?哪些变形函数会使得索引维数变化?具体如何变化?

【问题三】 请举出一个除了上文提过的关于哑变量方法的例子。

【问题四】 使用完stack后立即使用unstack一定能保证变化结果与原始表完全一致吗?

【问题五】 透视表中涉及了三个函数,请分别使用它们完成相同的目标(任务自定)并比较哪个速度最快。

【问题六】 既然melt起到了stack的功能,为什么再设计stack函数?

练习

(a)

data = pd.read_csv('data/Drugs.csv')
display(data.head())
result = pd.pivot_table(data, index=['State','COUNTY','SubstanceName'], columns='YYYY', values='DrugReports',
                        fill_value='-').reset_index().rename_axis(columns={'YYYY':''})
display(result.head())

(b)

result_ = result.melt(id_vars=result.columns[:3], 
            value_vars=result.columns[-8:],
            var_name='YYYY',value_name='DrugReports')
display(result_.head()) # 发现需要将里面的-转换成np.nan,再删掉缺失值就可以
# 也可以可以使用query直接进行筛选
result_ = result_.query('DrugReports != "-"')
display(result_.head())
result_ = result_.sort_values(by=['YYYY', 'State','COUNTY', 'SubstanceName']).\
          reset_index().drop(columns='index')
display(result_.head())