洗牌数据帧行

我有以下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
...

我怎样才能做到这一点?

753493 次浏览

您可以通过使用洗牌索引来洗牌数据帧的行。为此,您可以使用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)

使用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。也就是说,即使参考对象已经改变(我的意思是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)


您可以简单地使用sklearn

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

太长别读np.random.shuffle(ndarray)可以完成这项工作。
所以,在你的情况下

np.random.shuffle(DataFrame.values)

DataFrame,在引擎盖下,使用NumPy ndarray作为数据持有者。(您可以从DataFrame源代码查看)

因此,如果您使用np.random.shuffle(),它将沿着多维数组的第一轴打乱数组。但是DataFrame的索引仍然未打乱。

不过,有几点需要考虑。

  • 函数不返回任何值。如果您想保留原始对象的副本,则必须在传递给函数之前这样做。
  • 正如用户tj89建议的那样,sklearn.utils.shuffle()可以指定random_state以及另一个控制输出的选项。您可能需要它用于开发目的。
  • sklearn.utils.shuffle()更快。但是会将DataFrame的轴信息(索引、列)与它包含的ndarray一起交换。

基准测试结果

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

ndarray

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)

在这种情况下,通过取一个示例数组来洗牌熊猫数据帧索引并随机化其顺序,然后将数组设置为数据帧的索引。现在根据索引对数据帧进行排序。这是你洗牌的数据帧

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

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

这里有另一种方法来做到这一点:

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

(我没有足够的声誉来评论这篇文章,所以我希望别人能为我做这件事。有人担心第一种方法:

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

这意味着该方法是没有返回相同的对象,正如上次注释中建议的那样。所以这个方法确实做了一个洗牌的复制

这里有另一种方法:

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

同样有用的是,如果您将其用于Machine_learning并希望始终分离相同的数据,您可以使用:

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

这确保了你的随机选择总是可以复制的

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

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

在哪里

frac=1表示数据帧的所有行

random_state=42意味着在每次执行中保持相同的顺序

reset_index(Drop=True)意味着重新初始化随机数据帧的索引

通过传递frac参数,使用sample()对DataFrame进行洗牌。 将打乱的DataFrame保存到一个新变量。

new_variable = DataFrame.sample(frac=1)

我提议:

for x in df.columns:
np.random.seed(42);
np.random.shuffle(df[x].values)

我用一列任意长度的字符串(使用dtype: object)进行测试,它比@haku的答案快30倍,大概是因为它避免了创建可能昂贵的副本。

我的变体比接受的@Kris'es答案快3倍,这似乎也没有避免复制(基于Linuxtop中的RES列)。

如果您想洗牌所有值,但保留行和列名称。

df_c = df.copy()
df_c.iloc[:,:] = df_c.sample(frac=1,random_state=123,ignore_index=True)