在pandas中对DataFrame进行洗牌/排列

84

如何在pandas中以行或列的方式对数据框进行简单有效的洗牌?即如何编写一个函数shuffle(df, n, axis=0),该函数接受一个数据框,一定数量的洗牌次数n,和轴(axis=0表示行,axis=1表示列),并返回一个被洗牌n次后的数据框的副本。

编辑:关键是在不破坏数据框的行/列标签的情况下完成此操作。如果只是洗牌df.index,那么将会失去所有这些信息。我希望结果df与原始数据框相同,只是行或列的顺序不同。

编辑2:我的问题表述不清。当我说洗牌行时,我指的是分别对每行进行洗牌。因此,如果你有两列ab,我希望每行都被独立地洗牌,这样你就不会像整个重排每行那样拥有对ab之间的相同关联。类似于:

for 1...n:
  for each col in df: shuffle column
return new_df

但希望比朴素循环更有效率。这段代码对我来说无效:

def shuffle(df, n, axis=0):
        shuffled_df = df.copy()
        for k in range(n):
            shuffled_df.apply(np.random.shuffle(shuffled_df.values),axis=axis)
        return shuffled_df

df = pandas.DataFrame({'A':range(10), 'B':range(10)})
shuffle(df, 5)

请看下面这个简单的pandas解决方案:https://dev59.com/EmUo5IYBdhLWcg3wxB7T#47112434 - Ted Petrou
你的回答确实回答了问题,但似乎不是人们正在寻找的答案。 - cs95
10个回答

229

使用numpy的random.permutation函数:

In [1]: df = pd.DataFrame({'A':range(10), 'B':range(10)})

In [2]: df
Out[2]:
   A  B
0  0  0
1  1  1
2  2  2
3  3  3
4  4  4
5  5  5
6  6  6
7  7  7
8  8  8
9  9  9


In [3]: df.reindex(np.random.permutation(df.index))
Out[3]:
   A  B
0  0  0
5  5  5
6  6  6
3  3  3
8  8  8
7  7  7
9  9  9
1  1  1
2  2  2
4  4  4

30
+1,因为这正是我正在寻找的(尽管事实证明它不是OP想要的)。 - Doug Paul
4
如果存在重复项等情况,也可以使用 df.iloc[np.random.permutation(np.arange(len(df)))](对于数据集较大的情况可能更快)。 - Andy Hayden
3
好的方法。不过有没有一种可以原地进行的方法呢? - Andrew
3
针对我所使用的Python版本v3.6和Pandas版本v0.20.1,我需要将df.reindex(np.random.permutation(df.index))替换为df.set_index(np.random.permutation(df.index))才能达到想要的效果。 - Emanuel
1
在使用 set_index 后,就像 Emanuel 一样,我也需要 df.sort_index(inplace=True) - Shadi
这个方法不再适用了。在运行 Python 3.6.5、NumPy 1.15.0 和 Pandas 0.23.3 的情况下,唯一有效的解决方法是 Andy Hayden 的 df.iloc[np.random.permutation(np.arange(len(df)))] - Sindarus

98

抽样会随机化数据,因此只需对整个数据框进行抽样。

df.sample(frac=1)

正如@Corey Levinson所指出的,当你重新赋值时必须小心:

df['column'] = df['column'].sample(frac=1).reset_index(drop=True)

9
请注意,如果您想重新分配列,您需要执行 df['column'] = df['column'].sample(frac=1).reset_index(drop=True) - Corey Levinson

43
In [16]: def shuffle(df, n=1, axis=0):     
    ...:     df = df.copy()
    ...:     for _ in range(n):
    ...:         df.apply(np.random.shuffle, axis=axis)
    ...:     return df
    ...:     

In [17]: df = pd.DataFrame({'A':range(10), 'B':range(10)})

In [18]: shuffle(df)

In [19]: df
Out[19]: 
   A  B
0  8  5
1  1  7
2  7  3
3  6  2
4  3  4
5  0  1
6  9  0
7  4  6
8  2  8
9  5  9

2
我如何在这里区分行与列的混洗? - user248237
15
警告:我原本以为df.apply(np.random.permutation)可以作为解决方案,比df.reindex(np.random.permutation(df.index))更简洁,但实际上它们的行为不同。后者维护了同一行中列之间的关联性,而前者没有。这是我的误解,但希望能够避免其他人犯同样的错误。 - gozzilli
1
在这个上下文中,“np”是什么? - Sledge
1
numpy。通常会这样做: import numpy as np - Aku
1
我只想做一次洗牌,所以我只使用了 df.apply(np.random.shuffle, index=1),但是这似乎没有任何作用,打印结果的 df 看起来与输入完全相同。如果我执行 df = df.apply( ... ),我会得到一个带有 NaN 的 Series。如果我执行 df.apply( ... inplace=True),那么我会得到一个错误。 - Veggiet
显示剩余3条评论

23

您可以使用sklearn.utils.shuffle()需要sklearn 0.16.1或更高版本来支持Pandas数据框):

# Generate data
import pandas as pd
df = pd.DataFrame({'A':range(5), 'B':range(5)})
print('df: {0}'.format(df))

# Shuffle Pandas data frame
import sklearn.utils
df = sklearn.utils.shuffle(df)
print('\n\ndf: {0}'.format(df))

输出:

df:    A  B
0  0  0
1  1  1
2  2  2
3  3  3
4  4  4


df:    A  B
1  1  1
0  0  0
3  3  3
4  4  4
2  2  2

你可以使用 df.reset_index() 来重置索引列,如果需要的话:

df = df.reset_index(drop=True)
print('\n\ndf: {0}'.format(df)

输出结果:

df:    A  B
0  1  1
1  0  0
2  4  4
3  2  2
4  3  3

顺便提一下,df.sample(frac=1) 的速度略快一些(对于 400k 行数据来说,是 76.9 毫秒与 78.9 毫秒之间的差异)。 - m-dz

10

在pandas中一个简单的解决方案是独立使用sample方法在每一列上。使用apply迭代每一列:

df = pd.DataFrame({'a':[1,2,3,4,5,6], 'b':[1,2,3,4,5,6]})
df

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

df.apply(lambda x: x.sample(frac=1).values)

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

必须使用.value以返回numpy array而不是Series,否则返回的Series将与原始DataFrame对齐,什么也不改变:

df.apply(lambda x: x.sample(frac=1))

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

谢谢@Ted,这正是我来这里的原因。非常准确! - trazoM
我通过执行 np.random.shuffle(df['b'].values) 来对单个列进行了洗牌。请注意,np.random.shuffle() 会直接修改你的数据框。 - trazoM

6

从文档使用sample()

In [79]: s = pd.Series([0,1,2,3,4,5])

# When no arguments are passed, returns 1 row.
In [80]: s.sample()
Out[80]: 
0    0
dtype: int64

# One may specify either a number of rows:
In [81]: s.sample(n=3)
Out[81]: 
5    5
2    2
4    4
dtype: int64

# Or a fraction of the rows:
In [82]: s.sample(frac=0.5)
Out[82]: 
5    5
4    4
1    1
dtype: int64

4

我采用了对@root答案的稍微改进并直接使用原始值的方法。当然,这意味着您失去了进行复杂索引的能力,但它非常适合仅对数据进行混洗。

In [1]: import numpy

In [2]: import pandas

In [3]: df = pandas.DataFrame({"A": range(10), "B": range(10)})    

In [4]: %timeit df.apply(numpy.random.shuffle, axis=0)
1000 loops, best of 3: 406 µs per loop

In [5]: %%timeit
   ...: for view in numpy.rollaxis(df.values, 1):
   ...:     numpy.random.shuffle(view)
   ...: 
10000 loops, best of 3: 22.8 µs per loop

In [6]: %timeit df.apply(numpy.random.shuffle, axis=1)
1000 loops, best of 3: 746 µs per loop

In [7]: %%timeit                                      
for view in numpy.rollaxis(df.values, 0):
    numpy.random.shuffle(view)
   ...: 
10000 loops, best of 3: 23.4 µs per loop

请注意,numpy.rollaxis将指定的轴移动到第一维,然后让我们迭代剩余维度的数组。例如,如果我们想要沿着第一维(列)进行洗牌,则需要将第二维滚动到前面,以便在第一维上应用洗牌视图。
In [8]: numpy.rollaxis(df, 0).shape
Out[8]: (10, 2) # we can iterate over 10 arrays with shape (2,) (rows)

In [9]: numpy.rollaxis(df, 1).shape
Out[9]: (2, 10) # we can iterate over 2 arrays with shape (10,) (columns)

你的最终函数采用了一个诀窍,使结果符合将函数应用于轴的期望:

def shuffle(df, n=1, axis=0):     
    df = df.copy()
    axis = int(not axis) # pandas.DataFrame is always 2D
    for _ in range(n):
        for view in numpy.rollaxis(df.values, axis):
            numpy.random.shuffle(view)
    return df

3

当您想要对索引进行洗牌时,这可能更有用。

def shuffle(df):
    index = list(df.index)
    random.shuffle(index)
    df = df.ix[index]
    df.reset_index()
    return df

使用新索引选择新的df,然后重置它们。


2
我知道这个问题是关于一个 pandas 数据框的,但是如果洗牌是按行进行的(列顺序改变,行顺序不变),那么列名就不重要了,使用一个 np.array 可能会更有趣,然后 np.apply_along_axis() 就是你要找的东西。
如果这可行的话,这将是有帮助的,注意很容易切换数据被洗牌的轴线。
如果你的panda数据框命名为“df”,也许你可以:
1. 用“values = df.values”获取数据框的值, 2. 从“values”创建一个“np.array” 3. 应用下面显示的方法以按行或列洗牌“np.array” 4. 从洗牌后的“np.array”重新创建一个新的(洗牌后的)pandas df。

原始数组

a = np.array([[10, 11, 12], [20, 21, 22], [30, 31, 32],[40, 41, 42]])
print(a)
[[10 11 12]
 [20 21 22]
 [30 31 32]
 [40 41 42]]

保持行顺序,在每行内随机洗牌列。
print(np.apply_along_axis(np.random.permutation, 1, a))
[[11 12 10]
 [22 21 20]
 [31 30 32]
 [40 41 42]]

保持列的顺序,对每个列中的行进行随机排序

print(np.apply_along_axis(np.random.permutation, 0, a))
[[40 41 32]
 [20 31 42]
 [10 11 12]
 [30 21 22]]

原始数组不会被改变

print(a)
[[10 11 12]
 [20 21 22]
 [30 31 32]
 [40 41 42]]

0

如果你只想打乱 DataFrame 的子集,这里是我发现的一个解决方法:

shuffle_to_index = 20
df = pd.concat([df.iloc[np.random.permutation(range(shuffle_to_index))], df.iloc[shuffle_to_index:]])

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