@dlm 的 answer 很棒,但从 v0.20.0 开始,skiprows does accept a callable。可调用对象接收行号作为参数。
还请注意,他们对未知文件长度的回答依赖于对文件进行两次迭代——一次获取长度,另一次读取 csv。我在这里有三个解决方案,它们只依赖于遍历文件一次,尽管它们都有权衡。
解决方案 1:近似百分比
如果您可以指定百分之几你想要的行数,而不是多少行,你甚至不需要获取文件大小,你只需要通读文件一次。假设第一行有一个标题:
import pandas as pd
import random
p = 0.01 # 1% of the lines
# keep the header, then take only 1% of lines
# if random from [0,1] interval is greater than 0.01 the row will be skipped
df = pd.read_csv(
filename,
header=0,
skiprows=lambda i: i>0 and random.random() > p
)
正如 cmets 中所指出的,这只给出了大约正确的行数,但我认为它满足了所需的用例。
解决方案 2:每第 N 行
比第一个随机性要小得多,但给出了所需的确切行数。根据文件的排序方式,这可能符合您的用例。
n = 100 # every 100th line = 1% of the lines
df = pd.read_csv(filename, header=0, skiprows=lambda i: i % n != 0)
解决方案 3:油藏取样
(2021 年 7 月添加)
Reservoir sampling 是一种优雅的算法,用于从长度未知但您只能看到一次的流中随机选择 k 项目。
最大的优势是您可以在磁盘上没有完整数据集的情况下使用它,并且它可以在不知道完整数据集大小的情况下为您提供精确大小的样本。缺点是我没有看到在纯熊猫中实现它的方法,我认为你需要进入 python 来读取文件,然后再构造数据框。因此,您可能会丢失 read_csv 的某些功能或需要重新实现它,因为我们没有使用 pandas 来实际读取文件。
采用 Oscar Benjamin here 的算法实现:
from math import exp, log, floor
from random import random, randrange
from itertools import islice
from io import StringIO
def reservoir_sample(iterable, k=1):
"""Select k items uniformly from iterable.
Returns the whole population if there are k or fewer items
from https://bugs.python.org/issue41311#msg373733
"""
iterator = iter(iterable)
values = list(islice(iterator, k))
W = exp(log(random())/k)
while True:
# skip is geometrically distributed
skip = floor( log(random())/log(1-W) )
selection = list(islice(iterator, skip, skip+1))
if selection:
values[randrange(k)] = selection[0]
W *= exp(log(random())/k)
else:
return values
def sample_file(filepath, k):
with open(filepath, 'r') as f:
header = next(f)
result = [header] + sample_iter(f, k)
df = pd.read_csv(StringIO(''.join(result)))
reservoir_sample 函数返回一个字符串列表,每个字符串都是一行,所以我们只需要在最后把它变成一个数据框。这里假设只有一个标题行,我还没有考虑如何将其扩展到其他情况。
我在本地对此进行了测试,它比其他两种解决方案快得多。使用 550 MB 的 csv(来自 NYC TLC 的 2020 年 1 月“黄色出租车旅行记录”),解决方案 3 运行时间约为 1 秒,而其他两个运行时间约为 3-4 秒。
在我的测试中,这甚至比使用shuf 的@Bar 的answer 快一点(~10-20%),这让我很吃惊。