27

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

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MjM5ODU3OTIyOA%3D%3D&%3Bmid=2650680255&%3Bidx=2&%3Bsn=0c87f0586c5adcb75f0652344faacb26
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

2UvQzmJ.gif

点击上方“蓝字”关注“AI开发者”

2QjM3uJ.jpg!web

vme2Ij3.jpg!web

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

bYBF3q6.png!web

资料来源:Businessbroadway

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

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

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

文章结构:

  1. Pandas fillna 概述

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

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

   Pandas fillna 概述

e2MRjy6.jpg!web

图片来自 Pixabay

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

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

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

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

例子:

zYjqeaJ.png!web

rMjAFzV.png!web

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

QFjAjyb.jpg!web

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

7VfM3iz.png!web

男孩和女孩的体重 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 将得到:

A7R3Qjn.png!web

男孩和女孩权重的 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

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

AVZnia6.png!web

用样本平均值代替缺失值

# 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))

)

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

bYRZNbB.png!web

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

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

VB7Nv2u.png!web

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) 选择五个随机行,从而使你有一个偏差更小的数据可视化图。

2EJJRnr.png!web

下载数据帧中的数据示例

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

b6name2.png!web

每年有数据的国家数量

# 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 行数据。然而,这些观察结果现在是无效的。

bYNbamF.png!web

扩展数据帧,所有国家在 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 函数在末尾和开头进行插值和外推,结果是:

nYR7b2Q.png!web

很完美!现在我们有样本中所有国家 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

/ 更多阅读 /

3mmE3qZ.jpg!web

nQZJjub.gif点击 阅读原文 ,查看:什么是好的编程语言?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK