点击上方 "蓝字" 关注 "AI 开发者"
根据 Businessbroadway 的一项分析, 数据专业人员将会花高达 60% 的时间用于收集, 清理和可视化数据.
清理和可视化数据的一个关键方面是如何处理丢失的数据. Pandas 以 fillna 方法的形式提供了一些基本功能. 虽然 fillna 在最简单的情况下工作得很好, 但只要数据中的组或数据顺序变得相关, 它就会出现问题. 本文将讨论解决这些更复杂情况的技术.
这些情况通常是发生在由不同的区域 (时间序列), 组甚至子组组成的数据集上. 不同区域情况的例子有月, 季(通常是时间范围) 或一段时间的大雨. 性别也是数据中群体的一个例子, 子组的例子有年龄和种族.
这篇文章附带了代码. 所以你可以随意启动一个 Notebook, 直接开始.
文章结构:
Pandas fillna 概述
当排序不相关时, 处理丢失的数据
当排序相关时, 处理丢失的数据
Pandas fillna 概述
图片来自 Pixabay
Pandas 有三种通过调用 fillna()处理丢失数据的模式:
method='ffill':ffill 或 forward fill 向前查找非空值, 直到遇到另一个非空值
method='bfill':bfill 或 backward fill 将第一个观察到的非空值向后传播, 直到遇到另一个非空值
显式值: 也可以设置一个精确的值来替换所有的缺失值. 例如, 这个替换值可以是 -999, 以表示缺少该值.
例子:
当排序不相关时, 处理丢失的数据
来自 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 有两个驼峰. 有人可能会得出结论, 在我们的样本中有一个子组的女孩体重较重. 因为我们预先构建了分布, 所以我们知道情况并非如此. 但如果这是真实的数据, 我们可能会从中得出错误的结论.
男孩和女孩的体重 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 将得到:
男孩和女孩权重的 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
绘制数据图, 会出现一些奇怪的双峰分布(后面有代码).
用样本平均值代替缺失值
- # 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))
- )
运行上述代码片段将生成更清晰的曲线:
按年龄, 性别分组的体重 KDE 用各组的平均值代替缺失值
当顺序相关时, 处理丢失的数据
Jake Hills 在 Unsplash 上的照片
在处理时间序列数据时, 经常会出现两种情况:
调整日期范围: 假设你有一份关于各国的 GDP, 教育水平和人口年增长率的数据. 对一些国家来说, 你缺失了最初几年, 最后几年或者中间几年的数据. 当然, 你可以忽略它们. 不过, 为了可视化, 你可能想要填充这些数据.
插值: 看时间序列数据插值, 你会发现排序变得非常相关. 如果用基于截至 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) 选择五个随机行, 从而使你有一个偏差更小的数据可视化图.
下载数据帧中的数据示例
让我们看看我们每年有多少国家的数据.
每年有数据的国家数量
- # PLOT CODE:
- df.groupby(['Year']).size().plot(
- kind='bar',
- title='Number of countries with data',
- figsize=(10,5)
- )
我们可以看到, 特别是在早些年, 我们没有多少国家的数据, 而且整个样本周期都有一些波动. 为了减轻丢失数据的影响, 我们将执行以下操作:
按国家分组并重新索引到整个日期范围
在对每个国家分组的范围之外的年份内插和外推
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 行数据. 然而, 这些观察结果现在是无效的.
扩展数据帧, 所有国家在 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 函数在末尾和开头进行插值和外推, 结果是:
很完美! 现在我们有样本中所有国家 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
- / 更多阅读 /
点击 阅读原文 , 查看: 什么是好的编程语言?
来源: http://www.tuicool.com/articles/6bem2ai