熊猫有条件地创建系列/数据框列

Posted

技术标签:

【中文标题】熊猫有条件地创建系列/数据框列【英文标题】:Pandas conditional creation of a series/dataframe column 【发布时间】:2019-11-19 12:59:44 【问题描述】:

我有一个如下所示的数据框:

    Type       Set
1    A          Z
2    B          Z           
3    B          X
4    C          Y

我想将另一列添加到数据帧(或生成一系列),其长度与数据帧相同(记录/行数相等),如果Set == 'Z' 设置颜色'green',如果@'red' 987654325@ 等于其他任何值。

最好的方法是什么?

【问题讨论】:

【参考方案1】:

如果您只有两个选择:

df['color'] = np.where(df['Set']=='Z', 'green', 'red')

例如,

import pandas as pd
import numpy as np

df = pd.DataFrame('Type':list('ABBC'), 'Set':list('ZZXY'))
df['color'] = np.where(df['Set']=='Z', 'green', 'red')
print(df)

产量

  Set Type  color
0   Z    A  green
1   Z    B  green
2   X    B    red
3   Y    C    red

如果您有两个以上的条件,请使用np.select。例如,如果您希望 color

yellow(df['Set'] == 'Z') & (df['Type'] == 'A') 否则blue(df['Set'] == 'Z') & (df['Type'] == 'B') 否则purple(df['Type'] == 'B') 否则black,

然后使用

df = pd.DataFrame('Type':list('ABBC'), 'Set':list('ZZXY'))
conditions = [
    (df['Set'] == 'Z') & (df['Type'] == 'A'),
    (df['Set'] == 'Z') & (df['Type'] == 'B'),
    (df['Type'] == 'B')]
choices = ['yellow', 'blue', 'purple']
df['color'] = np.select(conditions, choices, default='black')
print(df)

产生

  Set Type   color
0   Z    A  yellow
1   Z    B    blue
2   X    B  purple
3   Y    C   black

【讨论】:

必须使用numpy的原因是什么? 这是 pandas 构建的库。因为它已经有了这个功能,所以不需要pandas 来实现它。 此代码现在(2022 年 1 月)返回 A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead【参考方案2】:

列表推导是另一种有条件地创建另一列的方法。如果您在列中使用对象 dtype,例如在您的示例中,列表推导通常优于大多数其他方法。

示例列表理解:

df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]

%timeit 测试:

import pandas as pd
import numpy as np

df = pd.DataFrame('Type':list('ABBC'), 'Set':list('ZZXY'))
%timeit df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]
%timeit df['color'] = np.where(df['Set']=='Z', 'green', 'red')
%timeit df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

1000 loops, best of 3: 239 µs per loop
1000 loops, best of 3: 523 µs per loop
1000 loops, best of 3: 263 µs per loop

【讨论】:

请注意,对于更大的数据帧(想想pd.DataFrame('Type':list('ABBC')*100000, 'Set':list('ZZXY')*100000)-size),numpy.where 超过 map,但列表理解是王道(比 numpy.where 快大约 50%)。 如果条件需要多列信息,是否可以使用列表推导法?我正在寻找这样的东西(这不起作用):df['color'] = ['red' if (x['Set'] == 'Z') & (x['Type'] == 'B') else 'green' for x in df] 将 iterrows 添加到数据框,然后您可以通过 row 访问多个列: ['red' if (row['Set'] == 'Z') & (row['Type'] = = 'B') else 'green' 用于索引,在 df.iterrows() 中的行] 请注意,如果您需要从数据框中的另一个系列中获取替换值,例如df['color_type'] = np.where(df['Set']=='Z', 'green', df['Type']),这个不错的解决方案将不起作用 @cheekybastard 或者不要,因为.iterrows() 是出了名的迟钝,在迭代时不应该修改 DataFrame。【参考方案3】:

实现这一目标的另一种方法是

df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

【讨论】:

【参考方案4】:

以下比定时here的方法要慢,但是我们可以根据多列的内容计算额外的列,并且可以为额外的列计算两个以上的值。

仅使用“Set”列的简单示例:

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

考虑更多颜色和更多列的示例:

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    elif row["Type"] == "C":
        return "blue"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C   blue

编辑(21/06/2019):使用 plydata

也可以使用plydata 来做这种事情(不过这似乎比使用assignapply 还要慢)。

from plydata import define, if_else

简单if_else

df = define(df, color=if_else('Set=="Z"', '"red"', '"green"'))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

嵌套if_else:

df = define(df, color=if_else(
    'Set=="Z"',
    '"red"',
    if_else('Type=="C"', '"green"', '"blue"')))

print(df)                            
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B   blue
3   Y    C  green

【讨论】:

我们如何使用这种函数引用其他行?例如。 if row["Set"].shift(1) == "Z":,但这不起作用 @ChrisDixon 据我所知,apply 只能看到一行或一列(取决于选择的轴),但不能看到当前处理的行或列之外的其他行或列。跨度> 【参考方案5】:

这是给这只猫换皮的另一种方法,使用字典将新值映射到列表中的键:

def map_values(row, values_dict):
    return values_dict[row]

values_dict = 'A': 1, 'B': 2, 'C': 3, 'D': 4

df = pd.DataFrame('INDICATOR': ['A', 'B', 'C', 'D'], 'VALUE': [10, 9, 8, 7])

df['NEW_VALUE'] = df['INDICATOR'].apply(map_values, args = (values_dict,))

它是什么样子的:

df
Out[2]: 
  INDICATOR  VALUE  NEW_VALUE
0         A     10          1
1         B      9          2
2         C      8          3
3         D      7          4

当您要创建许多 ifelse-type 语句(即要替换许多唯一值)时,这种方法会非常强大。

当然你也可以这样做:

df['NEW_VALUE'] = df['INDICATOR'].map(values_dict)

但在我的机器上,这种方法比上面的 apply 方法慢三倍以上。

你也可以这样做,使用dict.get:

df['NEW_VALUE'] = [values_dict.get(v, None) for v in df['INDICATOR']]

【讨论】:

我喜欢这个答案,因为它展示了如何对值进行多次替换 但在我的机器上,这种方法比上面的 apply 方法慢三倍以上。 你是如何对这些进行基准测试的?根据我的快速测量,.map() 解决方案比.apply() 快约 10 倍。 更新:在 100,000,000 行,52 个字符串值上,.apply() 需要 47 秒,而 .map() 只需 5.91 秒。【参考方案6】:

您可以简单地使用强大的.loc 方法并根据需要使用一个或多个条件(使用pandas=1.0.5 测试)。

代码摘要:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))
df['Color'] = "red"
df.loc[(df['Set']=="Z"), 'Color'] = "green"

#practice!
df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

解释:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))

# df so far: 
  Type Set  
0    A   Z 
1    B   Z 
2    B   X 
3    C   Y

添加一个“颜色”列并将所有值设置为“红色”

df['Color'] = "red"

应用你的单一条件:

df.loc[(df['Set']=="Z"), 'Color'] = "green"


# df: 
  Type Set  Color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

或多个条件,如果你想:

df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

您可以在此处阅读 Pandas 逻辑运算符和条件选择: Logical operators for boolean indexing in Pandas

【讨论】:

【参考方案7】:

你可以使用pandas方法wheremask

df['color'] = 'green'
df['color'] = df['color'].where(df['Set']=='Z', other='red')
# Replace values where the condition is False

df['color'] = 'red'
df['color'] = df['color'].mask(df['Set']=='Z', other='green')
# Replace values where the condition is True

或者,您可以将方法 transform 与 lambda 函数一起使用:

df['color'] = df['Set'].transform(lambda x: 'green' if x == 'Z' else 'red')

输出:

  Type Set  color
1    A   Z  green
2    B   Z  green
3    B   X    red
4    C   Y    red

来自@chai 的性能比较:

import pandas as pd
import numpy as np
df = pd.DataFrame('Type':list('ABBC')*1000000, 'Set':list('ZZXY')*1000000)
 
%timeit df['color1'] = 'red'; df['color1'].where(df['Set']=='Z','green')
%timeit df['color2'] = ['red' if x == 'Z' else 'green' for x in df['Set']]
%timeit df['color3'] = np.where(df['Set']=='Z', 'red', 'green')
%timeit df['color4'] = df.Set.map(lambda x: 'red' if x == 'Z' else 'green')

397 ms ± 101 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
976 ms ± 241 ms per loop
673 ms ± 139 ms per loop
796 ms ± 182 ms per loop

【讨论】:

也更快: import pandas as pd import numpy as np df = pd.DataFrame('Type':list('ABBC')*1000000, 'Set':list('ZZXY' )*1000000) %timeit df['color1'] = 'red'; df['color1'].where(df['Set']=='Z','green') %timeit df['color2'] = ['red' if x == 'Z' else 'green' for x in df['Set']] %timeit df['color3'] = np.where(df['Set']=='Z', 'red', 'green') %timeit df['color4'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green') 397 ms ± 101 ms 每个循环(平均值±标准差。7 次运行,每个循环 1 个循环)976 ms ±每个循环 241 毫秒 每个循环 673 毫秒 ± 139 毫秒 每个循环 796 毫秒 ± 182 毫秒 @chai 将您的评价添加到我的回答中。谢谢!【参考方案8】:

.apply() 方法的一个班轮如下:

df['color'] = df['Set'].apply(lambda set_: 'green' if set_=='Z' else 'red')

之后,df 数据框如下所示:

>>> print(df)
  Type Set  color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

【讨论】:

【参考方案9】:

如果您只有 2 个选择,请使用 np.where()

df = pd.DataFrame('A':range(3))
df['B'] = np.where(df.A>2, 'yes', 'no')

如果您有超过 2 个选择,也许apply() 可以工作 输入

arr = pd.DataFrame('A':list('abc'), 'B':range(3), 'C':range(3,6), 'D':range(6, 9))

而arr是

    A   B   C   D
0   a   0   3   6
1   b   1   4   7
2   c   2   5   8

如果您希望 E 列成为 if arr.A =='a' then arr.B elif arr.A=='b' then arr.C elif arr.A == 'c' then arr.D else something_else

arr['E'] = arr.apply(lambda x: x['B'] if x['A']=='a' else(x['C'] if x['A']=='b' else(x['D'] if x['A']=='c' else 1234)), axis=1)

最后 arr 是

    A   B   C   D   E
0   a   0   3   6   0
1   b   1   4   7   4
2   c   2   5   8   8

【讨论】:

【参考方案10】:

如果您要处理海量数据,最好采用记忆化方法:

# First create a dictionary of manually stored values
color_dict = 'Z':'red'

# Second, build a dictionary of "other" values
color_dict_other = x:'green' for x in df['Set'].unique() if x not in color_dict.keys()

# Next, merge the two
color_dict.update(color_dict_other)

# Finally, map it to your column
df['color'] = df['Set'].map(color_dict)

当你有很多重复值时,这种方法最快。我的一般经验法则是在以下情况下记忆:data_size > 10**4 & n_distinct data_size/4

E.x.在一个案例中记忆 10,000 行具有 2,500 个或更少的不同值。

【讨论】:

好的,所以只有 2 个不同的值要映射,100,000,000 行,没有“记忆”需要 6.67 秒,有 9.86 秒。 100,000,000 行,52 个不同的值,其中 1 个映射到第一个输出值,其他 51 个都对应另一个:7.99 秒没有记忆,11.1 秒有。 你的值是随机排列的吗?还是他们背靠背?大熊猫的高速可能是由于缓存@AMC 你的值是随机排列的吗?还是它们是背靠背的? 值是随机的,使用random.choices() 选择。【参考方案11】:

来自pyjanitor 的case_when 函数是pd.Series.mask 的包装器,并为多种条件提供了可链接/方便的形式:

对于单个条件:

df.case_when(
    df.col1 == "Z",  # condition
    "green",         # value if True
    "red",           # value if False
    column_name = "color"
    )

  Type Set  color
1    A   Z  green
2    B   Z  green
3    B   X    red
4    C   Y    red

对于多个条件:

df.case_when(
    df.Set.eq('Z') & df.Type.eq('A'), 'yellow', # condition, result
    df.Set.eq('Z') & df.Type.eq('B'), 'blue',   # condition, result
    df.Type.eq('B'), 'purple',                  # condition, result
    'black',              # default if none of the conditions evaluate to True
    column_name = 'color'  
)
  Type  Set   color
1    A   Z  yellow
2    B   Z    blue
3    B   X  purple
4    C   Y   black

更多例子可以找到here

【讨论】:

以上是关于熊猫有条件地创建系列/数据框列的主要内容,如果未能解决你的问题,请参考以下文章

如何有条件地转换熊猫数据框列

如何在熊猫数据框列中选择一系列值?

如何有条件地从熊猫系列中选择项目

包含数组的熊猫系列

熊猫:考虑多种条件正确过滤数据框列

如何加快一系列文档中键的存在总和? - 熊猫,nltk