为什么随机抽样与数据集而不是样本量成比例? (pandas.sample() 示例)

Why does random sampling scale with the dataset not the sample size? (pandas .sample() example)

当从不同大小的分布中随机抽样时,我惊讶地发现执行时间似乎主要与被抽样的数据集的大小成比例,而不是被抽样的值的数量。示例:

import pandas as pd
import numpy as np
import time as tm

#generate a small and a large dataset
testSeriesSmall = pd.Series(np.random.randn(10000))
testSeriesLarge = pd.Series(np.random.randn(10000000))

sampleSize = 10
tStart = tm.time()
currSample = testSeriesLarge.sample(n=sampleSize).values
print('sample %d from %d values: %.5f s' % (sampleSize, len(testSeriesLarge), (tm.time() - tStart)))

tStart = tm.time()
currSample = testSeriesSmall.sample(n=sampleSize).values
print('sample %d from %d values: %.5f s' % (sampleSize, len(testSeriesSmall), (tm.time() - tStart)))

sampleSize = 1000
tStart = tm.time()
currSample = testSeriesLarge.sample(n=sampleSize).values
print('sample %d from %d values: %.5f s' % (sampleSize, len(testSeriesLarge), (tm.time() - tStart)))

tStart = tm.time()
currSample = testSeriesSmall.sample(n=sampleSize).values
print('sample %d from %d values: %.5f s' % (sampleSize, len(testSeriesSmall), (tm.time() - tStart)))

输出为:

sample 10 from 10000 values: 0.00126 s
sample 10 from 10000000 values: 1.10504 s
sample 1000 from 10000 values: 0.00122 s
sample 1000 from 10000000 values: 1.15000 s

这似乎违反直觉。也许我很密集,但问题似乎类似于生成随机索引列表,而且我希望采样值的数量很重要,而数据集的大小并不重要。我已经尝试了另一种或两种具有类似结果的实现,但我开始觉得我只是遗漏了一个基本问题。

我的问题有两个:(1) 这是 pandas 中的基本问题还是实施的怪癖? (2) 是否有一种明显更快的方法可以采用这种方式从大型数据集中随机抽样?

pandas.Series.sample() 在你的情况下归结为:

rs = np.random.RandomState()
locs = rs.choice(axis_length, size=n, replace=False)
return self.take(locs)

慢的部分是rs.choice():

%timeit rs.choice(100000000, size=1, replace=False)
1 loop, best of 3: 9.43 s per loop

生成单个随机数大约需要10秒!如果将第一个参数除以 10,则大约需要 1 秒。太慢了!

如果您使用 replace=True,速度会非常快。如果您不介意结果中出现重复条目​​,那么这就是您的一种解决方法。

choice(replace=False) 的 NumPy 文档说:

This is equivalent to np.random.permutation(np.arange(5))[:3]

这几乎可以解释问题——它会生成大量可能的值,将它们打乱顺序,然后取前 N 个。这是性能问题的根本原因,并且已在中报告为一个问题NumPy 在这里:https://github.com/numpy/numpy/pull/5158

在 NumPy 中显然很难修复,因为人们依赖 choice() 在使用相同的随机种子值时不会改变(在 NumPy 的版本之间)的结果。

由于您的用例非常狭窄,您可以这样做:

def sample(series, n):
    locs = np.random.randint(0, len(series), n*2)
    locs = np.unique(locs)[:n]
    assert len(locs) == n, "sample() assumes n << len(series)"
    return series.take(locs)

这提供了更快的时间:

sample 10 from 10000 values: 0.00735 s
sample 10 from 1000000 values: 0.00944 s
sample 10 from 100000000 values: 1.44148 s
sample 1000 from 10000 values: 0.00319 s
sample 1000 from 1000000 values: 0.00802 s
sample 1000 from 100000000 values: 0.01989 s
sample 100000 from 1000000 values: 0.05178 s
sample 100000 from 100000000 values: 0.93336 s

这看起来是一个内部 numpy 问题。我相信 pandas sample 方法调用 numpy.random.choice。让我们来看看 numpy 在各种数组大小和样本大小下的表现如何。

创建数组

large = np.arange(1000000)
small = np.arange(1000)

不放回样本计时

%timeit np.random.choice(large, 10, replace=False)
10 loops, best of 3: 27.4 ms per loop

%timeit np.random.choice(small, 10, replace=False)
10000 loops, best of 3: 41.4 µs per loop

用替换对样本计时

%timeit np.random.choice(large, 10, replace=True)
100000 loops, best of 3: 11.7 µs per loop

%timeit np.random.choice(small, 10, replace=True)
100000 loops, best of 3: 12.2 µs per loop

非常有趣的是,在不放回原样的情况下做样本时,大阵列的时间长了将近 3 个数量级,而它恰好大了三个数量级。这向我表明 numpy 正在对数组进行随机排序,然后取前 10 项。

放回抽样时,每个值都是独立选择的,所以时间是相同的。