在Pandas中对数据框的子集进行随机抽样

47

我有一个包含100,000行数据的 pandas DataFrame,想把它分成100个部分,每部分有1000行。

我该如何在这100个部分中随机抽取一部分的特定大小(例如50行)?由于df已经按照顺序排列,所以前1000行来自第一部分,下一个1000行来自另一部分,以此类推。


df.iloc[np.random.randint(1,1000,50),:]df1 是其中的 100 个部分之一。 - Abdou
5个回答

70
您可以使用 sample 方法*:
In [11]: df = pd.DataFrame([[1, 2], [3, 4], [5, 6], [7, 8]], columns=["A", "B"])

In [12]: df.sample(2)
Out[12]:
   A  B
0  1  2
2  5  6

In [13]: df.sample(2)
Out[13]:
   A  B
3  7  8
0  1  2

*关于DataFrames之一的部分。

注意:如果您的样本大小大于DataFrame的大小,则除非使用替换进行采样,否则会引发错误。

In [14]: df.sample(5)
ValueError: Cannot take a larger sample than population when 'replace=False'

In [15]: df.sample(5, replace=True)
Out[15]:
   A  B
0  1  2
1  3  4
2  5  6
3  7  8
1  3  4

2
请问replace函数是做什么用的?文档里写得不太清楚,请解释一下。谢谢! - hoang tran
1
@hoang 这需要进行“有放回抽样”,因此如果您有一个大小为5的数据集,您可以取一个大小为10的样本。此外,如果您取N个元素的样本,没有进行有放回抽样的情况下,将会包含每个元素,但是有放回抽样则不一定如此。例如,请参见https://www.statisticshowto.datasciencecentral.com/sampling-with-replacement-without/。 - Andy Hayden
6
@hoangtran,“replace” 的意思是在采样时是否重复采样。不重复采样的意思是一旦选取了一行,就不能再次选取(例如,我从袋子里抽出一个玻璃球,不放回去,因此我不能再次抽到它)。重复采样的意思是我可以再次采样同一行(例如,在抽出一个玻璃球后,我将其放回袋子中,然后再次抽取,这样我就可以再次得到相同的球)。 - goryh
@goryh 这会持续到什么时候?我的意思是,如果您在一定数量的迭代中重复执行此操作,最终应该得到一个空数据框,对吗? - Whynote
@whynote pandas.dataframe.sample() 实际上并不会改变数据框。我的“弹珠”解释是关于通常有或没有替换抽样的意义,而不是关于 panadas 如何实现它的。 - goryh
这不是答案 - 它忽略了从每个组中取样本的步骤,我已经添加了如何执行该步骤的答案。 - Jeff

12

一种解决方案是使用numpy中的choice函数。

假设你想要从100个条目中随机选取50个,你可以使用:

import numpy as np
chosen_idx = np.random.choice(1000, replace=False, size=50)
df_trimmed = df.iloc[chosen_idx]

当然,这并未考虑您的块结构。例如,如果您想要从块i中获取50个样本,则可以执行以下操作:

import numpy as np
block_start_idx = 1000 * i
chosen_idx = np.random.choice(1000, replace=False, size=50)
df_trimmed_from_block_i = df.iloc[block_start_idx + chosen_idx]

7
你可以在数据中添加一个 "section" 列,然后执行 groupby 和 sample:
import numpy as np
import pandas as pd

df = pd.DataFrame(
    {"x": np.arange(1_000 * 100), "section": np.repeat(np.arange(100), 1_000)}
)
# >>> df
#            x  section
# 0          0        0
# 1          1        0
# 2          2        0
# 3          3        0
# 4          4        0
# ...      ...      ...
# 99995  99995       99
# 99996  99996       99
# 99997  99997       99
# 99998  99998       99
# 99999  99999       99
#
# [100000 rows x 2 columns]

sample = df.groupby("section").sample(50)
# >>> sample
#            x  section
# 907      907        0
# 494      494        0
# 775      775        0
# 20        20        0
# 230      230        0
# ...      ...      ...
# 99740  99740       99
# 99272  99272       99
# 99863  99863       99
# 99198  99198       99
# 99555  99555       99
#
# [5000 rows x 2 columns]

如果您只对特定部分感兴趣,可以使用额外的.query("section == 42")或其他查询条件。

请注意,这需要 pandas 1.1.0 版本及以上,请参阅此处的文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.core.groupby.DataFrameGroupBy.sample.html

对于较早版本,请参见 @msh5678 的答案。


3
谢谢你,Jeff, 但是我收到了一个错误;
AttributeError: Cannot access callable attribute 'sample' of 'DataFrameGroupBy' objects, try using the 'apply' method

我建议使用以下命令,而不是 sample = df.groupby("section").sample(50)

df.groupby('section').apply(lambda grp: grp.sample(50))

1
Pandas 1.1.0 版本中新增了 groupby 示例,详见文档:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.core.groupby.DataFrameGroupBy.sample.html。感谢指出,我会更新我的答案。 - Jeff

0

这是一个递归的好地方。

def main2():
    rows = 8  # say you have 8 rows, real data will need len(rows) for int
    rands = []
    for i in range(rows):
        gen = fun(rands)
        rands.append(gen)
    print(rands)  # now range through random values


def fun(rands):
    gen = np.random.randint(0, 8)
    if gen in rands:
        a = fun(rands)
        return a
    else: return gen


if __name__ == "__main__":
    main2()

输出:[6, 0, 7, 1, 3, 5, 4, 2]


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