从多个其他变量创建 pandas 变量的 pythonic 方法是什么

What is the pythonic way of creating a pandas variable from multiple other variables

提问人:gowerc 提问时间:4/10/2018 最后编辑:gowerc 更新时间:4/10/2018 访问量:152

问:

我是一名 R 程序员,目前正在尝试学习 Python / Pandas。目前,我正在尝试解决如何从使用多个现有变量的函数中清晰干净地创建新变量的问题。

请注意,我的示例中使用的函数并不那么复杂,但我试图推广到可能更复杂或需要更多变量的任意函数的情况,也就是说,我试图避免针对此特定函数优化的解决方案,并更多地关注如何处理一般情况。

作为参考,这是我如何在 R 中执行此操作的示例。

library(tidyverse)

df <- data_frame(
    num = c(15, 52 , 24 , 29),
    cls = c("a" , "b" , "b", "a")
)

attempt1 <- function( num , cls){
    if ( cls == "a") return( num + 10)
    if ( cls == "b") return( num - 10)
}

## Example 1
df %>% 
    mutate( num2 = map2_dbl( num , cls , attempt1))

## Example 2
df %>% 
    mutate( num = ifelse( num <= 25 , num + 10 , num)) %>% 
    mutate( num2 = map2_dbl( num , cls , attempt1))

阅读 pandas 文档以及各种 SO 帖子,我发现了多种在 python 中实现这一目标的方法,但是它们都不适合我。作为参考,我在下面发布了我当前的 3 个解决方案:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "num" : [14, 52 , 24 , 29],
    "cls" : ["a" , "b" , "b" ,"a"]
})

### Example 1

def attempt1( num, cls):
    if cls == "a":
        return num + 10
    if cls == "b":
        return num - 10

df.assign( num2 = df.apply( lambda x: attempt1(x["num"] , x["cls"]) , axis = 1))


def attempt2( df):
    if df["cls"] == "a":
        return df["num"] + 10
    if df["cls"] == "b":
        return df["num"] - 10

df.assign( num2 = df.apply(attempt2, axis=1))



def attempt3(df):
    df["num2"] = attempt1(df["num"], df["cls"])
    return df

df.apply( attempt3 , axis = 1)



### Example 2

df.assign( num = np.where( df["num"] <= 25 , df["num"] + 10 , df["num"]))\
    .apply( attempt3 , axis = 1)

我对尝试 1 的问题是它看起来非常冗长。此外,您需要自行引用回起始数据集,这意味着如果要将多个派生链接在一起,则必须将数据集写出到中间变量,即使您无意保留它。

Attempt2 的语法明显更简洁,但仍然存在中间变量问题。另一个问题是,该函数需要一个数据帧,这使得函数更难进行单元测试,灵活性较低,并且不太清楚输入应该是什么。

就功能而言,Attempt3 对我来说似乎是最好的,因为它为您提供了一个清晰的可测试功能,并且不需要保存中间数据集。主要的缺点是你现在必须有 2 个函数,这感觉像是多余的代码。

任何帮助或建议将不胜感激。

Python Pandas 数据帧

评论

0赞 jpp 4/10/2018
如果您定义“Pythonic”的含义,那将会有所帮助。Pythonic(或者,在这种情况下,Pandonic / Pandorable)意味着完全摆脱。这在一般和特定情况下都是如此。pd.DataFrame.apply

答:

1赞 Guybrush 4/10/2018 #1

您可以依靠它来完成这项工作,方法是创建一个包含 的列,然后根据 的值将其更改为 。然后,您可以使用该列根据需要执行算术运算。Series.where10-10cls

https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.where.html

分步(详细)示例:

df['what_to_add'] = 10
df['what_to_add'] = df['what_to_add'].where(df['cls'] == 'a', -10)
df['num'] = df['num'] + df['what_to_add']

假设两个数字相反,另一种可能性是为操作数的符号定义一列:

df['sign'] = 1 - 2 * (df['cls'] == 'a').astype(int)
df['num'] = df['num'] + df['sign'] * 10

第三种方法是使用 ,以便将“a”替换为 10,“b”替换为 -10:replace

df['what_to_add'] = df['cls'].replace(['a', 'b'], [10, -10])
df['num'] = df['num'] + df['what_to_add']

已编辑: 或者,按照JPP(https://stackoverflow.com/a/49748695/4582949)的建议,使用:map

df['num2'] += df['cls'].map({'a': 10, 'b': -10})

评论

0赞 gowerc 4/10/2018
您好,感谢您抽出宝贵时间回复。很抱歉很痛苦,但我希望将解决方案保留为核心输入的函数,以便可以抽象和单元测试推导。尽管您分享的内容适用于此示例(感谢您的帮助),但我不确定它将如何扩展到更复杂的推导或需要多个输入的推导。
0赞 Guybrush 4/10/2018
你能详细说明一下吗?我不明白:)
0赞 gowerc 4/10/2018
当然 - 澄清一下,我想我希望找到一种形式的解决方案,其中乐趣是一次从每列中获取 1 个值的任何任意函数(因此我使用了 apply)。例如,假设我示例中的函数是: 对于任意数字 n1 / n2 和任意字符 c3(如果有帮助,很乐意为此设置 testdata)df["new_var"] = fun( df["var1"] , df["var2"] , ... )def (n1 , n2 , c3): if n1 > n2 and c3 in ["a" , "b"]: return n2 + n1 * 2 if n2**3 == n1/2 and c3 not in ["c"] return(n1) else: return(n2 / 2)
0赞 Guybrush 4/10/2018
这可以通过以下方式完成,但是使用任意函数意味着您不会从 pandas/numpy 性能中受益:-/您应该尽可能多地尝试直接在数组上工作。 可以帮助您,从 if holds 中获取值,否则。.applynumpy.where(cond, x, y)xcondy
1赞 jpp 4/10/2018 #2

一种有效的方法是使用:pd.Series.map

df['num2'] += df['cls'].map({'a': 10, 'b': -10})

这使用字典将值映射到 10 或 -10。cls

还有许多其他方法(参见 @Guybrush 的答案),但基于字典的方法对于较大的数据帧是可扩展且有效的。在我看来,它也是可读的。

相关:通过字典有效地替换 pandas 系列中的值

评论

0赞 gowerc 4/10/2018
您好,感谢您抽出宝贵时间回复。很抱歉很痛苦,但我希望将解决方案保留为核心输入的函数,以便可以抽象和单元测试推导。尽管您分享的内容适用于此示例(感谢您的帮助),但我不确定它将如何扩展到更复杂的推导或需要多个输入的推导。
0赞 jpp 4/10/2018
@CroGo,感谢您的反馈。不确定我是否关注你。(1)这是核心输入的函数;(2)字典是Python原生的,在unittest中可以很容易地将字典与测试对象进行比较;(3)该方法具有极强的可扩展性。您的字典可以有一百万个条目;这仍将有效工作。
0赞 gowerc 4/10/2018
当然 - 澄清一下,我想我希望找到一种形式的解决方案,其中乐趣是一次从每列中获取 1 个值的任何任意函数(因此我使用了 apply)。例如,假设我示例中的函数是: 对于任意数字 n1 / n2 和任意字符 c3(如果有帮助,很乐意为此设置 testdata)df["new_var"] = fun( df["var1"] , df["var2"] , ... )def (n1 , n2 , c3): if n1 > n2 and c3 in ["a" , "b"]: return n2 + n1 * 2 if n2**3 == n1/2 and c3 not in ["c"] return(n1) else: return(n2 / 2)
0赞 jpp 4/10/2018
@CroGo,这仍然很容易矢量化。请注意,自定义逻辑将在 Python 级别的循环中逐行工作,而不是矢量化的方式。自定义函数明确避免使用矢量化功能。我明白你关于分离“赋值函数”的观点——如果这是你正在做的事情,你可以在没有.(只需将您的数据保存在列表列表中即可!pd.DataFrame.applypandaspandas