利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

雷锋网  •  扫码分享
我是创始人李岩:很抱歉!给自己产品做个广告,点击进来看看。  

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

根据 Businessbroadway 的一项分析,数据专业人员将会花高达 60% 的时间用于收集、清理和可视化数据。

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

资料来源:Businessbroadway

清理和可视化数据的一个关键方面是如何处理丢失的数据。Pandas 以 fillna 方法的形式提供了一些基本功能。虽然 fillna 在最简单的情况下工作得很好,但只要数据中的组或数据顺序变得相关,它就会出现问题。本文将讨论解决这些更复杂情况的技术。

这些情况通常是发生在由不同的区域(时间序列)、组甚至子组组成的数据集上。不同区域情况的例子有月、季(通常是时间范围)或一段时间的大雨。性别也是数据中群体的一个例子,子组的例子有年龄和种族。

这篇文章附带了代码。所以你可以随意启动一个 Notebook,直接开始。

文章结构:

  1. Pandas fillna 概述

  2. 当排序不相关时,处理丢失的数据

  3. 当排序相关时,处理丢失的数据

Pandas fillna 概述

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

图片来自 Pixabay

Pandas 有三种通过调用 fillna()处理丢失数据的模式:

  • method='ffill':ffill 或 forward fill 向前查找非空值,直到遇到另一个非空值

  • method='bfill':bfill 或 backward fill 将第一个观察到的非空值向后传播,直到遇到另一个非空值

  • 显式值:也可以设置一个精确的值来替换所有的缺失值。例如,这个替换值可以是 -999,以表示缺少该值。

例子:

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

当排序不相关时,处理丢失的数据

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

来自 Pixabay 公共领域的图片

通常,在处理丢失的数据时,排序并不重要,因此,用于替换丢失值的值可以基于可用数据的整体来决定。在这种情况下,你通常会用你猜测的最佳值(即,可用数据的平均值或中等值)替换丢失的值。

让我们快速回顾一下为什么应该小心使用此方法。假设你调查了 1000 个男孩和 1000 个女孩的体重。不幸的是,在收集数据的过程中,有些数据丢失了。

# imports

import numpy as np


# sample 1000 boys and 1000 girls

boys = np.random.normal(70,5,1000)

girls = np.random.normal(50,3,1000)


# unfortunately, the intern running the survey on the girls got distracted and lost 100 samples

for i in range(100):

        girls[np.random.randint(0,1000)] = np.nan


# build DataFrame

boys = pd.DataFrame(boys, columns=['weight'])

boys['gender'] = 'boy'


girls = pd.DataFrame(girls, columns=['weight'])

girls['gender'] = 'girl'


df = pd.concat([girls,boys],axis=0)

df['weight'] = df['weight'].astype(float)

子组

如果不是很在意缺失值填充什么,我们可以用整个样本的平均值填充缺失的值。不过,结果看起来有些奇怪。女孩的 KDE 有两个驼峰。有人可能会得出结论,在我们的样本中有一个子组的女孩体重较重。因为我们预先构建了分布,所以我们知道情况并非如此。但如果这是真实的数据,我们可能会从中得出错误的结论。

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

男孩和女孩的体重 KDE,我们用样本均值替换缺失的数据(下附代码)

# PLOT CODE:

sns.set_style('white')

fig, ax = plt.subplots(figsize=(16, 7))


mean = df['weight'].mean()


sns.distplot(

       df[df['gender'] == 'girl']['weight'].fillna(mean),

       kde=True,

       hist=False,

       ax=ax,

       label='girls'

)


sns.distplot(

       df[df['gender'] == 'boy']['weight'],

       kde=True,

       hist=False,

       ax=ax,

       label='boys'

)


plt.title('Kernel density estimation of weight for boys and girls')


sns.despine()

用组的平均值填充缺失值

在这种情况下,Pandas 的转换函数就派上了用场,它使用变换提供了一种简洁的方法来解决这个问题:

df['filled_weight'] = df.groupby('gender')['weight'].transform(

lambda grp: grp.fillna(np.mean(grp))

)

运行上述命令并绘制填充的权重值的 KDE 将得到:

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

男孩和女孩权重的 KDE,我们用组平均值替换缺失值(下面附代码)

# PLOT CODE:

sns.set_style('white')

fig, ax = plt.subplots(figsize=(16, 7))


sns.distplot(

       df[df['gender'] == 'girl']['filled_weight'],

       kde=True,

       hist=False,

       ax=ax,

       label='girls'

)

sns.distplot(

       df[df['gender'] == 'boy']['filled_weight'],

       kde=True,

       hist=False,

       ax=ax,

       label='boys'

)


plt.title('Kernel density estimation of weight for boys and girls')


sns.despine()

多个子组

让我们使用前面的例子,但是这次,我们进一步将数据细分为年龄组。我们先创建一些模拟数据:

# paramter for the weight distribution (mean, std)

param_map = {

        'boy':{

              '<10':(40,4),

              '<20':(60,4),

              '20+':(70,5),

        },

       'girl':{

              '<10':(30,2),

              '<20':(40,3),

              '20+':(50,3),

       }

}


# generate 10k records

df = pd.DataFrame({

       'gender':np.random.choice(['girl','boy'],10000),

       'age_cohort':np.random.choice(['<10','<20','20+'],10000)

})


# set random weight based on parameters

df['weight'] = df.apply(

       lambda x: np.random.normal(

              loc=param_map[x['gender']][x['age_cohort']][0],

              scale=param_map[x['gender']][x['age_cohort']][1]

       ),axis=1

)


# set 500 values missing

for i in range(500):

       df.loc[np.random.randint(0,len(df)),'weight'] = np.nan

绘制数据图,会出现一些奇怪的双峰分布(后面有代码)。

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

用样本平均值代替缺失值

# PLOT CODE

df['filled_weight'] = df['weight'].fillna(

        df['weight'].mean()

)


g = sns.FacetGrid(

       df,

       col='age_cohort',

       row='gender',

       col_order=['<10','<20','20+']

)


g.map(sns.kdeplot,'filled_weight')

现在,如果我们只用性别的平均值来代替缺失的值,就远远不够,因为男孩和女孩不仅体重不同,而且不同年龄组的体重也大不相同。

幸运的是,可以像前面一样使用转换。我们将对两列进行分组,代码如下:

df['filled_weight'] = df.groupby(['gender','age_cohort'])

['weight'].transform(

        lambda grp: grp.fillna(np.mean(grp))

)

运行上述代码片段将生成更清晰的曲线:

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

按年龄、性别分组的体重 KDE 用各组的平均值代替缺失值

当顺序相关时,处理丢失的数据

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

Jake Hills 在 Unsplash 上的照片

在处理时间序列数据时,经常会出现两种情况:

  1. 调整日期范围:假设你有一份关于各国的 GDP、教育水平和人口年增长率的数据。对一些国家来说,你缺失了最初几年、最后几年或者中间几年的数据。当然,你可以忽略它们。不过,为了可视化,你可能想要填充这些数据。

  2. 插值:看时间序列数据插值,你会发现排序变得非常相关。如果用基于截至 2019 年的数据计算出的平均值来替换 2012 年丢失的股票数据,势必会产生一些古怪的结果。

我们将以《2019 年世界幸福报告》(World Happiness Report 2019)中的数据为基础来看一个例子,在这个例子中,我们将处理这两种情况。《世界幸福报告》试图回答影响全世界幸福的因素。该报告调查了 2005 年至 2018 年的数据。

载入数据

# Load the data

df = pd.read_csv('https://raw.githubusercontent.com/FBosler/you- datascientist/master/happiness_with_continent.csv')

样本检验

与 df.head(5)相反,df.sample(5) 选择五个随机行,从而使你有一个偏差更小的数据可视化图。

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

下载数据帧中的数据示例

让我们看看我们每年有多少国家的数据。

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

每年有数据的国家数量

# PLOT CODE:

df.groupby(['Year']).size().plot(

       kind='bar',

       title='Number of countries with data',

       figsize=(10,5)

)

我们可以看到,特别是在早些年,我们没有多少国家的数据,而且整个样本周期都有一些波动。为了减轻丢失数据的影响,我们将执行以下操作:

  1. 按国家分组并重新索引到整个日期范围

  2. 在对每个国家分组的范围之外的年份内插和外推

1.按国家分组并重新索引日期范围

# Define helper function

def add_missing_years(grp):

        _ = grp.set_index('Year')

       _ = _.reindex(list(range(2005,2019)))

       del _['Country name']

       return _


# Group by country name and extend

df = df.groupby('Country name').apply(add_missing_years)

df = df.reset_index()

我们现在大约有 600 行数据。然而,这些观察结果现在是无效的。

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

扩展数据帧,所有国家在 2005 年到 2018 年间都有数据

2.在对每个国家分组的范围之外的年份内插和外推

# Define helper function

def fill_missing(grp):

        res = grp.set_index('Year')\

       .interpolate(method='linear',limit=5)\

       .fillna(method='ffill')\

       .fillna(method='bfill')

       del res['Country name']

       return res


# Group by country name and fill missing

df = df.groupby(['Country name']).apply(

       lambda grp: fill_missing(grp)

)


df = df.reset_index()

fill_missing 函数在末尾和开头进行插值和外推,结果是:

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

很完美!现在我们有样本中所有国家 2005 年至 2018 年的数据。当我写这篇关于可视化的文章时,上面的方法对我来说很有意义。如果你想了解更多关于这篇报告的信息,可以查看: https://towardsdatascience.com/plotting-with-python-c2561b8c0f1f?source=post_page-----cb6ccf060531---------------------- 。

via: https://towardsdatascience.com/using-pandas-transform-and-apply-to-deal-with-missing-data-on-a-group-level-cb6ccf060531    

雷锋网年度评选—— 寻找19大行业的最佳AI落地实践

创立于2017年的「AI最佳掘金案例年度榜单」,是业内首个人工智能商业案例评选活动。雷锋网从商用维度出发,寻找人工智能在各个行业的最佳落地实践。

第三届评选已正式启动,关注微信公众号“雷锋网”,回复关键词“榜单”参与报名。详情可咨询微信号:xqxq_xq

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

雷锋网雷锋网 (公众号:雷锋网) 雷锋网

雷锋网版权文章,未经授权禁止转载。详情见。

利用 Pandas 的 transform 和 apply 来处理组级别的缺失数据

随意打赏

提交建议
微信扫一扫,分享给好友吧。