ChatGPT解决这个技术问题 Extra ChatGPT

随机播放 DataFrame 行

我有以下数据框:

    Col1  Col2  Col3  Type
0      1     2     3     1
1      4     5     6     1
...
20     7     8     9     2
21    10    11    12     2
...
45    13    14    15     3
46    16    17    18     3
...

DataFrame 是从 CSV 文件中读取的。所有具有 Type 1 的行都在顶部,然后是具有 Type 2 的行,然后是具有 Type 3 的行,依此类推。

我想打乱 DataFrame 行的顺序,以便混合所有 Type。一个可能的结果可能是:

    Col1  Col2  Col3  Type
0      7     8     9     2
1     13    14    15     3
...
20     1     2     3     1
21    10    11    12     2
...
45     4     5     6     1
46    16    17    18     3
...

我怎样才能做到这一点?


M
M.Innat

使用 Pandas 执行此操作的惯用方法是使用数据框的 .sample 方法对所有行进行采样而不进行替换:

df.sample(frac=1)

frac 关键字参数指定要在随机样本中返回的行的分数,因此 frac=1 表示返回所有行(以随机顺序)。

注意:如果您希望就地改组您的数据框并重置索引,您可以执行例如

df = df.sample(frac=1).reset_index(drop=True)

在这里,指定 drop=True 可防止 .reset_index 创建包含旧索引条目的列。

后续说明:虽然上面的操作看起来可能不是就地,但python/pandas 足够聪明,不会为洗牌的对象再做一次malloc。也就是说,即使 reference 对象发生了变化(我的意思是 id(df_old)id(df_new) 不同),底层 C 对象仍然是相同的。为了证明确实如此,您可以运行一个简单的内存分析器:

$ python3 -m memory_profiler .\test.py
Filename: .\test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     68.5 MiB     68.5 MiB   @profile
     6                             def shuffle():
     7    847.8 MiB    779.3 MiB       df = pd.DataFrame(np.random.randn(100, 1000000))
     8    847.9 MiB      0.1 MiB       df = df.sample(frac=1).reset_index(drop=True)


是的,这正是我想在我的第一条评论中展示的内容,您必须两次分配必要的内存,这与就地执行相去甚远。
@m-dz 如果我错了,请纠正我,但如果你不这样做 .copy() 你仍然引用同一个底层对象。
好的,当我有时间时,我将使用内存分析器运行它。谢谢
不,它不会复制 DataFrame,只需看看这一行:github.com/pandas-dev/pandas/blob/v0.23.0/pandas/core/…
@m-dz 我在上面运行了一个内存分析器。请参阅更新答案中的“后续说明”。
M
M.Innat

为此,您可以简单地使用 sklearn

from sklearn.utils import shuffle
df = shuffle(df)

这很好,但您可能需要在改组后重置索引: df.reset_index(inplace=True, drop=True)
M
M.Innat

您可以通过使用打乱索引进行索引来打乱数据帧的行。为此,您可以例如使用 np.random.permutation(但 np.random.choice 也是一种可能):

In [12]: df = pd.read_csv(StringIO(s), sep="\s+")

In [13]: df
Out[13]: 
    Col1  Col2  Col3  Type
0      1     2     3     1
1      4     5     6     1
20     7     8     9     2
21    10    11    12     2
45    13    14    15     3
46    16    17    18     3

In [14]: df.iloc[np.random.permutation(len(df))]
Out[14]: 
    Col1  Col2  Col3  Type
46    16    17    18     3
45    13    14    15     3
20     7     8     9     2
0      1     2     3     1
1      4     5     6     1
21    10    11    12     2

如果您想保持索引编号为 1、2、..、n,如您的示例所示,您可以简单地重置索引:df_shuffled.reset_index(drop=True)


M
M.Innat

TL;DRnp.random.shuffle(ndarray) 可以胜任。
所以,就你而言

np.random.shuffle(DataFrame.values)

DataFrame 在底层使用 NumPy ndarray 作为数据持有者。 (您可以从 DataFrame source code 检查)

因此,如果您使用 np.random.shuffle(),它将沿多维数组的第一个轴对数组进行洗牌。但是 DataFrame 的索引保持不变。

不过,有几点需要考虑。

函数没有返回。如果您想保留原始对象的副本,则必须在传递给函数之前这样做。

正如用户 tj89 所建议的,sklearn.utils.shuffle() 可以指定 random_state 以及另一个选项来控制输出。您可能希望将其用于开发目的。

sklearn.utils.shuffle() 更快。但是会随机排列 DataFrame 的轴信息(索引,列)以及它包含的 ndarray。

基准测试结果

sklearn.utils.shuffle()np.random.shuffle() 之间。

数组

nd = sklearn.utils.shuffle(nd)

0.10793248389381915 秒。快 8 倍

np.random.shuffle(nd)

0.8897626010002568 秒

数据框

df = sklearn.utils.shuffle(df)

0.3183923360193148 秒。快 3 倍

np.random.shuffle(df.values)

0.9357550159329548 秒

结论:如果可以将轴信息(索引,列)与 ndarray 一起洗牌,请使用 sklearn.utils.shuffle()。否则,使用 np.random.shuffle()

使用的代码

import timeit
setup = '''
import numpy as np
import pandas as pd
import sklearn
nd = np.random.random((1000, 100))
df = pd.DataFrame(nd)
'''

timeit.timeit('nd = sklearn.utils.shuffle(nd)', setup=setup, number=1000)
timeit.timeit('np.random.shuffle(nd)', setup=setup, number=1000)
timeit.timeit('df = sklearn.utils.shuffle(df)', setup=setup, number=1000)
timeit.timeit('np.random.shuffle(df.values)', setup=setup, number=1000)


df = df.sample(frac=1) 不做与 df = sklearn.utils.shuffle(df) 完全相同的事情吗?根据我的测量,df = df.sample(frac=1) 更快,并且似乎执行完全相同的操作。他们也都分配了新的内存。 np.random.shuffle(df.values) 最慢,但不分配新内存。
在将轴与数据一起洗牌方面,它似乎可以做同样的事情。是的,使用上面相同的代码,df.sample(frac=1) 似乎比 sklearn.utils.shuffle(df) 快 20%。或者您可以执行 sklearn.utils.shuffle(ndarray) 以获得不同的结果。
...并且对索引进行洗牌确实不好,因为它可能导致难以跟踪某些功能的问题,这些功能要么重置索引,要么依赖于基于行数的关于最大索引的假设。例如,这发生在 h2o_model.predict() 上,它会重置返回的预测帧的索引。
M
M.Innat

以下可能是其中一种方式:

dataframe = dataframe.sample(frac=1, random_state=42).reset_index(drop=True)

在哪里

frac=1 表示一个数据框的所有行

random_state=42 表示在每次执行中保持相同的顺序

reset_index(drop=True) 表示为随机数据帧重新初始化索引


M
M.Innat

(我没有足够的声誉在顶帖上发表评论,所以我希望其他人可以为我这样做。)有人担心第一种方法:

df.sample(frac=1)

它进行深层复制或只是更改了数据框。我运行了以下代码:

print(hex(id(df)))
print(hex(id(df.sample(frac=1))))
print(hex(id(df.sample(frac=1).reset_index(drop=True))))

我的结果是:

0x1f8a784d400
0x1f8b9d65e10
0x1f8b9d65b70

这意味着该方法没有返回相同的对象,正如上一条评论中所建议的那样。所以这个方法确实做了一个洗牌的副本。


请查看原始答案的后续说明。在那里您会看到,即使引用发生了变化(不同的 id),底层对象也没有被复制。换句话说,该操作实际上是在内存中进行的(尽管这并不明显)。
我希望底层的 ndarray 是相同的,但迭代器是不同的(并且是随机的),因此尽管元素顺序发生了变化,但内存消耗的变化最小。
M
M.Innat

还有什么有用的,如果您将它用于 Machine_learning 并希望始终分离相同的数据,您可以使用:

df.sample(n=len(df), random_state=42)

这样可以确保您始终保持随机选择可复制


使用 frac=1 你不需要 n=len(df)
M
M.Innat

这是另一种方法:

df_shuffled = df.reindex(np.random.permutation(df.index))

请注意,这会更改原始 df 中的索引,并生成一个副本,您将其保存到 df_shuffled 中。但是,更令人担忧的是,任何不依赖于索引的东西,例如 `df_shuffled.iterrows()' 都会产生与 df 完全相同的顺序。总之,慎用!
@Jblasco 这是不正确的,原来的 df 根本 没有 改变。 np.random.permutation 的文档:“...如果 x 是一个数组,则制作一个 copy 并随机打乱元素”。 DataFrame.reindex 的文档:“除非新索引等同于当前索引并且 copy=False,否则会生成一个新对象”。所以答案是完全安全的(尽管会生成一个副本)。
@AndreasSchörgenhumer,感谢您指出这一点,您部分正确!我知道我已经尝试过了,所以我做了一些测试。不管 np.random.permutation says 的文档是什么,并且取决于 numpy 的版本,你会得到我描述的效果或你提到的效果。与 numpy > 1.15.0,创建一个数据框并做一个普通的 np.random.permutation(df.index),原始 df 中的索引发生了变化。对于 numpy == 1.14.6,情况并非如此。因此,我比以往任何时候都更重复我的警告:由于不可预见的副作用和版本依赖性,这种做事方式很危险。
@Jblasco您是对的,谢谢您提供的详细信息。我正在运行 numpy 1.14,所以一切正常。对于 numpy 1.15,某处似乎有一个 bug 。鉴于此错误,您的警告目前确实是正确的。但是,由于它是一个 错误 并且文档说明了其他行为,我仍然坚持我之前的声明,即答案是安全的(鉴于文档确实反映了实际行为,我们通常应该能够依靠)。
@AndreasSchörgenhumer,说实话,不太确定这是一个错误还是一个功能。文档保证数组的副本,而不是 Index 类型...无论如何,我的建议/警告基于实际行为,而不是文档:p
A
Abhilash Reddy Yammanuru

通过在这种情况下获取样本数组索引并随机化其顺序,然后将数组设置为数据帧的索引,从而对 pandas 数据帧进行洗牌。现在根据索引对数据框进行排序。这是你的洗牌数据框

import random
df = pd.DataFrame({"a":[1,2,3,4],"b":[5,6,7,8]})
index = [i for i in range(df.shape[0])]
random.shuffle(index)
df.set_index([index]).sort_index()

输出

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

在上面的代码中将您的数据框插入我的位置。


我更喜欢这种方法,因为这意味着如果我需要通过将随机索引存储到变量来精确地重现我的算法输出,则可以重复洗牌。
M
M.Innat

通过传递 frac 参数,使用 sample() 对 DataFrame 进行随机播放。将洗牌后的 DataFrame 保存到一个新变量中。

new_variable = DataFrame.sample(frac=1)

M
M.Innat

这是另一种方式:

df['rnd'] = np.random.rand(len(df))
df = df.sort_values(by='rnd', inplace=True).drop('rnd', axis=1)