【问题标题】:Pandas - using assign and if-else statement in method chainingPandas - 在方法链接中使用 assign 和 if-else 语句
【发布时间】:2019-08-22 12:51:04
【问题描述】:

我来自 R 背景,我正在尝试在 pandas 中复制 dplyr 中的 mutate() 函数。

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

data = {'name': ['Jason', 'Molly', 'Tina', 'Jake', 'Amy'], 
        'age': [42, 52, 36, 24, 73], 
        'preTestScore': [4, 24, 31, 2, 3],
        'postTestScore': [25, 94, 57, 62, 70]}
df = pd.DataFrame(data, columns = ['name', 'age', 'preTestScore', 'postTestScore'])

我现在尝试使用assign 方法创建一个名为age_bracket 的新列,如下所示:

(df.
    assign(age_bracket= lambda x: "under 25" if x['age'] < 25 else
        ("25-34" if x['age'] < 35 else "35+"))

这引发了以下我无法理解的错误:

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()

我对以下解决方案不感兴趣:

df['age_bracket'] = np.where(df.age < 25, 'under 25',
     (np.where(df.age < 35, "25-34", "35+")))

因为我不希望底层 df 改变。我试图在方法链接方面做得更好,我可以在不改变底层 df 的情况下以不同的方式快速探索我的 df。

有什么建议吗?

【问题讨论】:

  • df.assign(age_bracket=np.where(df.age &lt; 25, 'under 25', (np.where(df.age &lt; 35, "25-34", "35+"))))?

标签: python pandas dplyr method-chaining


【解决方案1】:

这是可能的,但不推荐,因为循环(在 apply 函数的底层):

df = (df.
    assign(age_bracket= lambda x: x['age'].apply(lambda y: "under 25" if y < 25 else
        ("25-34" if y < 35 else "35+"))))
print (df)
    name  age  preTestScore  postTestScore age_bracket
0  Jason   42             4             25         35+
1  Molly   52            24             94         35+
2   Tina   36            31             57         35+
3   Jake   24             2             62    under 25
4    Amy   73             3             70         35+

numpy.select:

df = df.assign(age_bracket= np.select([df.age < 25,df.age < 35], ['under 25', "25-34"], "35+"))

但最好在这里使用cut

df = (df.assign(age_bracket= lambda x: pd.cut(x['age'], 
                                              bins=[0, 25, 35, 150],
                                              labels=["under 25", "25-34", "35+"])))

【讨论】:

  • 谢谢。我将更多地探索“切割”。你能帮我理解为什么我们在assign里面需要apply吗?
  • 也感谢cut 上的工作示例。你会说cut 是比应用更快的方法吗?
  • @RachitKinger - 我认为是的,但取决于数据、行数、箱数。真实数据中的最佳测试。
  • 是的。 pd.cut 特别适合将数值分组到(有序)类别(例如满意度/难度/年龄桶)中。干得好!
【解决方案2】:

为什么不将 assign 与 np.where 一起使用?

df.assign(age_bracket = np.where(df.age < 25, 'under 25',
     (np.where(df.age < 35, "25-34", "35+"))))

您将返回原始数据框的副本,其中包含新列。

但我同意@jezrael pd.cut 是更好的我的意见。

输出:

    name  age  preTestScore  postTestScore age_bracket
0  Jason   42             4             25         35+
1  Molly   52            24             94         35+
2   Tina   36            31             57         35+
3   Jake   24             2             62    under 25
4    Amy   73             3             70         35+

【讨论】:

    【解决方案3】:

    使用datardatar

    >>> from datar.all import f, tibble, mutate, if_else
    >>> 
    >>> data = {'name': ['Jason', 'Molly', 'Tina', 'Jake', 'Amy'], 
    ...         'age': [42, 52, 36, 24, 73], 
    ...         'preTestScore': [4, 24, 31, 2, 3],
    ...         'postTestScore': [25, 94, 57, 62, 70]}
    >>> 
    >>> df = tibble(**data)
    >>> df >> mutate(age_bracket=if_else(
    ...   f.age < 25, 
    ...   "under 25",
    ...   if_else(f.age < 35, "25-34", "35+")
    ... ))
          name     age  preTestScore  postTestScore age_bracket
      <object> <int64>       <int64>        <int64>    <object>
    0    Jason      42             4             25         35+
    1    Molly      52            24             94         35+
    2     Tina      36            31             57         35+
    3     Jake      24             2             62    under 25
    4      Amy      73             3             70         35+
    

    免责声明:我是datar 包的作者。

    【讨论】:

      【解决方案4】:

      pyjanitordev 中有一个case_when 实现,在这种情况下可能会有所帮助,实现思想的灵感来自pydatatable 中的if_else 和R 的data.table 中的fcase;在引擎盖下,它使用pd.Series.mask:

      # pip install git+https://github.com/pyjanitor-devs/pyjanitor.git
      import pandas as pd
      import janitor as jn
      
      df.case_when(
         df.age.lt(25), 'under 25',  # 1st condition, result
         df.age.lt(35), '25-34',    # 2nd condition, result
         '35+',                     # default
         column_name = 'age_bracket')
      
          name  age  preTestScore  postTestScore age_bracket
      0  Jason   42             4             25         35+
      1  Molly   52            24             94         35+
      2   Tina   36            31             57         35+
      3   Jake   24             2             62    under 25
      4    Amy   73             3             70         35+
      

      但对于这个用例,由于您是按类别进行分区,@jezrael 的pd.cut 解决方案更有效。

      【讨论】:

        猜你喜欢
        • 2017-07-05
        • 1970-01-01
        • 2013-04-20
        • 2015-03-13
        • 1970-01-01
        • 2014-11-11
        • 1970-01-01
        相关资源
        最近更新 更多