如何将数字数据映射到 Pandas 数据框的类别/分组中

43

我有一个Pandas数据框:

SamplePandas

它大约有3百万行。有3种age_units:Y,D,W代表年,天和周。任何超过1岁的个体都有一个年龄单位为Y,我的第一组是<2岁,所以我要测试的年龄单位只有Y...

我想创建一个新列AgeRange并填充以下范围:

  • <2
  • 2 - 18
  • 18 - 35
  • 35 - 65
  • 65+

所以我写了一个函数

def agerange(values):
    for i in values:
        if complete.Age_units == 'Y':
            if complete.Age > 1 AND < 18 return '2-18'
            elif complete.Age > 17 AND < 35 return '18-35'
            elif complete.Age > 34 AND < 65 return '35-65'
            elif complete.Age > 64 return '65+'
        else return '< 2'

我原以为��果将整个数据框传入,就能得到所需的内容,然后可以像这样创建我想要的列:

agedetails['age_range'] = ageRange(agedetails)

但是当我尝试运行第一段代码来创建函数时,出现了以下错误:

  File "<ipython-input-124-cf39c7ce66d9>", line 4
    if complete.Age > 1 AND complete.Age < 18 return '2-18'
                          ^
SyntaxError: invalid syntax

显然它不接受AND - 但是我记得在课上听说过可以这样使用AND?我一定是弄错了,那么正确的方法是什么呢?

因此,在遇到错误后,我甚至不确定传入数据框的方法是否会引发错误。我猜可能是肯定的。如果是这样 - 那我该怎么做才能使它也起作用呢?

我想学习最好的方法,但对我来说,最好的方法之一就是保持简单,即使这意味着要分几个步骤进行...


3
@jpp下面的回答很棒——另外请注意您的语法错误,应该使用小写字母and。在if语句条件后,需要使用冒号“:”,所以应该写成if complete.Age > 1 and complete.Age < 18: return '2-18' - gyx-hh
2个回答

86

使用Pandas时,应避免逐行操作,因为这通常涉及低效的Python级循环。以下是几种替代方案。

Pandas:pd.cut

如@JonClements所建议的那样,您可以使用pd.cut,其好处在于新列成为分类

您只需要定义边界(包括np.inf)和类别名称,然后将pd.cut应用于所需的数值列即可。

bins = [0, 2, 18, 35, 65, np.inf]
names = ['<2', '2-18', '18-35', '35-65', '65+']

df['AgeRange'] = pd.cut(df['Age'], bins, labels=names)

print(df.dtypes)

# Age             int64
# Age_units      object
# AgeRange     category
# dtype: object

NumPy:np.digitize

np.digitize提供了另一种简洁的解决方案。其思想是定义您的边界和名称,创建一个字典,然后将np.digitize应用于您的年龄列。最后,使用您的字典来映射您的类别名称。

请注意,在边界情况下,使用下限进行映射到一个箱子。

import pandas as pd, numpy as np

df = pd.DataFrame({'Age': [99, 53, 71, 84, 84],
                   'Age_units': ['Y', 'Y', 'Y', 'Y', 'Y']})

bins = [0, 2, 18, 35, 65]
names = ['<2', '2-18', '18-35', '35-65', '65+']

d = dict(enumerate(names, 1))

df['AgeRange'] = np.vectorize(d.get)(np.digitize(df['Age'], bins))

结果

   Age Age_units AgeRange
0   99         Y      65+
1   53         Y    35-65
2   71         Y      65+
3   84         Y      65+
4   84         Y      65+

2
或者...在bins的末尾添加float('inf')(或np.inf),然后使用:pd.cut(df.Age, bins, labels=names)...这样你就会得到一个分类系列而不是字符串... - Jon Clements
1
@jpp 这太棒了!感谢您费心提供如此清晰和深思熟虑的回复,并详细介绍了bins/pandas cut方法,这是锦上添花的完美之举。这是最简单、最优雅的方法,我一定会使用它,谢谢。在我所做的所有搜索中,我曾经看到过有关Bins的内容,但没有想到如何应用它,更不用说它有多么容易了!再次感谢! - kiltannen

1

这个任务也可以使用numpy方法完成。特别是,numpy.select可以在此处用于将数值数据转换为分类数据。它与OP中的if-else阶梯非常相似;只是条件在一个列表中,返回值在另一个列表中。

import numpy as np
conds = [df['Age']<2, df['Age'].between(2, 18), df['Age'].between(19, 35), df['Age'].between(36, 65)]
names = ['<2', '2-18', '18-35', '35-65', '65+']
df['AgeRange'] = np.select(conds, names[:-1], names[-1])

另一种方法是numpy.searchsorted。实际上,在幕后,pd.cut也实现了这种方法。基本思路是找到每个年龄在bins中插入的位置,以保持顺序(本质上就是分箱),并从names中选择相应的标签。
bins = [0, 2, 18, 35, 65, np.inf]
names = np.array(['<2', '2-18', '18-35', '35-65', '65+'])
df['AgeRange'] = names[np.searchsorted(bins, df['Age'])-1]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接