Pandas:从每行随机列中选择值

5
假设我有以下Pandas DataFrame:
df = pd.DataFrame({
    'a': [1, 2, 3],
    'b': [4, 5, 6],
    'c': [7, 8, 9]
})

    a   b   c
0   1   4   7
1   2   5   8
2   3   6   9

我想要生成一个新的 pandas.Series 对象,使得这个 Series 中的每个值都是从 DataFrame 的随机一列中按行选择的。因此,这个 Series 的一个可能的输出如下:

0    7
1    2
2    9
dtype: int64

(在第0行中它随机选择了“c”,在第1行中它随机选择了“a”,并在第2行再次随机选择了“c”)。

我知道可以通过迭代行并使用random.choice来选择每一行来实现此操作,但是迭代行不仅性能差,而且也是“非Pythonic”的。 此外,df.sample(axis=1)会选择整个列,因此所有列都将从同一列中选择,这不是我想要的。 有没有更好的方法使用向量化的pandas方法来做到这一点?

5个回答

5

可能是这样的:

pd.Series([np.random.choice(i,1)[0] for i in df.values])

1
如果你需要创建许多这样的列,我发现这种方法更加高效:newdf = df.apply(lambda row : row.sample(n=10000, replace=True).values, axis=1, result_type="expand") - Joseph Garvin

5
这里有一个完全矢量化的解决方案。请注意,它不使用 Pandas 方法,而是涉及底层 numpy 数组的操作。
import numpy as np

indices = np.random.choice(np.arange(len(df.columns)), len(df), replace=True)

示例输出为 [1, 2, 1],对应着 ['b', 'c', 'b']

然后使用此方法来切片numpy数组:

df['random'] = df.to_numpy()[np.arange(len(df)), indices]

结果:

   a  b  c  random
0  1  4  7       7
1  2  5  8       5
2  3  6  9       9

你能解释一下这个语法的部分 [np.arange(len(df)), indices] 是做什么的吗?我在谷歌上搜索时遇到了困难。 - Joseph Garvin
此外,您的答案在我的数据上给出了与@jfaccioni的答案非常不同的分布,这让我觉得这个答案有问题(使用您的答案会得到显著较小的平均值)。但是它确实运行得更快。 - Joseph Garvin
@JosephGarvin - 这个语法是对2D数组进行切片,使用两个相同长度的序列表示行索引和列索引。在这里,np.arange(len(df))是行索引 - 在上面的示例中它只是 [0, 1, 2]。随机选择的列索引存储在 indices 变量中(在示例中为 [1, 2, 1])。使用 [0, 1, 2][1, 2, 1] 进行切片将返回 (0, 1), (1, 2) 和 (2, 1) 处的值。 - sjw
@JosephGarvin - 我已经循环执行了几百万次我的解决方案,但在这个问题中使用的小型示例数据集中,结果看起来并没有什么异常。如果您可以分享一个数据集(或让我知道如何生成一个),该数据集能够展示我的解决方案与循环解决方案产生不同的结果,我会很感兴趣去看一下。 - sjw

2

这段代码可以完成任务(使用内置模块random):

最初的回答:

ddf = df.apply(lambda row : random.choice(row.tolist()), axis=1)

或者使用 pandas sample
ddf = df.apply(lambda row : row.sample(), axis=1)

两者行为相同。 ddf 是您的系列(Series)。最初的回答。

请注意,#1 可以简化为 df.apply(random.choice, axis=1) - tdy

1
pd.DataFrame(
    df.values[range(df.shape[0]), 
                   np.random.randint(
                       0, df.shape[1], size=df.shape[0])])

输出

    0
0   4
1   5
2   9

1
您可能仍需要在每行中迭代,同时选择每行的随机值 - 无论您是使用for循环显式地执行还是使用您决定调用的任何函数隐式地执行。
但是,如果适合您的风格,您可以使用列表推导式将其简化为单行:
result = pd.Series([random.choice(pd.iloc[i]) for i in range(len(df))])

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