前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >利用 Pandas 的 transform 和 apply 来处理组级别的丢失数据

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

作者头像
AI研习社
发布2019-11-27 23:28:56
1.8K0
发布2019-11-27 23:28:56
举报
文章被收录于专栏:AI研习社AI研习社

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

资料来源:Businessbroadway

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

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

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

文章结构:

  1. Pandas fillna 概述
  2. 当排序不相关时,处理丢失的数据
  3. 当排序相关时,处理丢失的数据

Pandas fillna 概述

图片来自 Pixabay

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

  • method='ffill':ffill 或 forward fill 向前查找非空值,直到遇到另一个非空值
  • method='bfill':bfill 或 backward fill 将第一个观察到的非空值向后传播,直到遇到另一个非空值
  • 显式值:也可以设置一个精确的值来替换所有的缺失值。例如,这个替换值可以是 -999,以表示缺少该值。

例子:

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

来自 Pixabay 公共领域的图片

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

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

代码语言:javascript
复制
# 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 有两个驼峰。有人可能会得出结论,在我们的样本中有一个子组的女孩体重较重。因为我们预先构建了分布,所以我们知道情况并非如此。但如果这是真实的数据,我们可能会从中得出错误的结论。

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

代码语言:javascript
复制
# 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 的转换函数就派上了用场,它使用变换提供了一种简洁的方法来解决这个问题:

代码语言:javascript
复制
df['filled_weight'] = df.groupby('gender')['weight'].transform(
lambda grp: grp.fillna(np.mean(grp))
)

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

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

代码语言:javascript
复制
# 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()

多个子组

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

代码语言:javascript
复制
# 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

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

用样本平均值代替缺失值

代码语言:javascript
复制
# 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')

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

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

代码语言:javascript
复制
df['filled_weight'] = df.groupby(['gender','age_cohort'])
['weight'].transform(
        lambda grp: grp.fillna(np.mean(grp))
)

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

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

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

Jake Hills 在 Unsplash 上的照片

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

  1. 调整日期范围:假设你有一份关于各国的 GDP、教育水平和人口年增长率的数据。对一些国家来说,你缺失了最初几年、最后几年或者中间几年的数据。当然,你可以忽略它们。不过,为了可视化,你可能想要填充这些数据。
  2. 插值:看时间序列数据插值,你会发现排序变得非常相关。如果用基于截至 2019 年的数据计算出的平均值来替换 2012 年丢失的股票数据,势必会产生一些古怪的结果。

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

载入数据

代码语言:javascript
复制
# 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) 选择五个随机行,从而使你有一个偏差更小的数据可视化图。

下载数据帧中的数据示例

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

每年有数据的国家数量

代码语言:javascript
复制
# PLOT CODE:
df.groupby(['Year']).size().plot(
       kind='bar',
       title='Number of countries with data',
       figsize=(10,5)
)

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

  1. 按国家分组并重新索引到整个日期范围
  2. 在对每个国家分组的范围之外的年份内插和外推

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

代码语言:javascript
复制
# 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 行数据。然而,这些观察结果现在是无效的。

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

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

代码语言:javascript
复制
# 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 函数在末尾和开头进行插值和外推,结果是:

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档