Pandas`isin`功能的更快替代品

use*_*212 17 python numpy pandas

我有一个非常大的数据框df,看起来像:

ID       Value1    Value2
1345      3.2      332
1355      2.2      32
2346      1.0      11
3456      8.9      322
Run Code Online (Sandbox Code Playgroud)

我有一个包含ID子集的列表ID_list.我需要的一个子集dfID包含在ID_list.

目前,我正在使用df_sub=df[df.ID.isin(ID_list)]它.但这需要很多时间.IDs中包含的ID_list没有任何模式,因此它不在一定范围内.(我需要对许多类似的数据帧应用相同的操作.我想知道是否有更快的方法来执行此操作.如果将make ID作为索引,它会有很大帮助吗?

谢谢!

cha*_*cyk 31

编辑2:这是一个链接到最近的各种pandas操作的性能,虽然它似乎似乎不包括合并和加入到目前为止.

https://github.com/mm-mansour/Fast-Pandas

编辑1:这些基准测试是针对一个相当古老的熊猫版本,可能还不相关.请参阅下面迈克的评论merge.

这取决于数据的大小,但对于大型数据集,DataFrame.join似乎是要走的路.这需要您的DataFrame索引为您的"ID",并且您要加入的系列或DataFrame具有的索引是您的"ID_list".该系列还必须具有一个name被使用的join,它被称为一个新的字段name.您还需要指定内部联接以获得类似isin因为join默认为左联接.查询in语法似乎具有与isin大型数据集相同的速度特性.

如果您正在处理小型数据集,则会得到不同的行为,使用列表解析或应用字典实际上比使用更快isin.

否则,您可以尝试使用Cython获得更高的速度.

# I'm ignoring that the index is defaulting to a sequential number. You
# would need to explicitly assign your IDs to the index here, e.g.:
# >>> l_series.index = ID_list
mil = range(1000000)
l = mil
l_series = pd.Series(l)

df = pd.DataFrame(l_series, columns=['ID'])


In [247]: %timeit df[df.index.isin(l)]
1 loops, best of 3: 1.12 s per loop

In [248]: %timeit df[df.index.isin(l_series)]
1 loops, best of 3: 549 ms per loop

# index vs column doesn't make a difference here
In [304]: %timeit df[df.ID.isin(l_series)]
1 loops, best of 3: 541 ms per loop

In [305]: %timeit df[df.index.isin(l_series)]
1 loops, best of 3: 529 ms per loop

# query 'in' syntax has the same performance as 'isin'
In [249]: %timeit df.query('index in @l')
1 loops, best of 3: 1.14 s per loop

In [250]: %timeit df.query('index in @l_series')
1 loops, best of 3: 564 ms per loop

# ID must be the index for DataFrame.join and l_series must have a name.
# join defaults to a left join so we need to specify inner for existence.
In [251]: %timeit df.join(l_series, how='inner')
10 loops, best of 3: 93.3 ms per loop

# Smaller datasets.
df = pd.DataFrame([1,2,3,4], columns=['ID'])
l = range(10000)
l_dict = dict(zip(l, l))
l_series = pd.Series(l)
l_series.name = 'ID_list'


In [363]: %timeit df.join(l_series, how='inner')
1000 loops, best of 3: 733 µs per loop

In [291]: %timeit df[df.ID.isin(l_dict)]
1000 loops, best of 3: 742 µs per loop

In [292]: %timeit df[df.ID.isin(l)]
1000 loops, best of 3: 771 µs per loop

In [294]: %timeit df[df.ID.isin(l_series)]
100 loops, best of 3: 2 ms per loop

# It's actually faster to use apply or a list comprehension for these small cases.
In [296]: %timeit df[[x in l_dict for x in df.ID]]
1000 loops, best of 3: 203 µs per loop

In [299]: %timeit df[df.ID.apply(lambda x: x in l_dict)]
1000 loops, best of 3: 297 µs per loop
Run Code Online (Sandbox Code Playgroud)

  • 我只是想补充一下(在18.1中的leas)我发现merge实际上是在加入,你不必担心搞乱索引.尝试类似`df.merge(pandas.DataFrame(value,columns = [dimension]),on = [dimension])`.至少对我来说,它能够在60%的时间内完成同样的任务. (4认同)
  • @phasselmann当然,使用与答案相同的语法,您可以将系列转换为像`l_df = l_series.to_frame()`这样的过滤器,然后您可以使用`filtered_df = df.merge(l_df, left_index=True, right_index=True)` 连接解决方​​案需要 10.2 毫秒,而合并解决方案需要 6.7 毫秒。 (2认同)

Nic*_*aro 7

是的,isin速度相当慢。

相反,创建ID索引然后使用 use会更快loc,例如:

df.set_index('ID', inplace=True)
df.loc[list_of_indices]
Run Code Online (Sandbox Code Playgroud)

实际上,我来到这个页面的原因是我需要df根据另一个 df 中的索引在 my 中创建一个标签:“如果 df_1 的索引与 df_2 的索引匹配,则将其标记为 1,否则为 NaN”,我是这样完成的:

df_2['label'] = 1  # Create a label column
df_1.join(df_2['label'])
Run Code Online (Sandbox Code Playgroud)

这也非常快。

  • 非常好的主意;尽管这假设索引字段是唯一的(PK);这可能不是真的。 (3认同)