【问题标题】:Check if value in pandas dataframe is within any two values of two other columns in another dataframe检查熊猫数据框中的值是否在另一个数据框中其他两列的任意两个值内
【发布时间】:2021-11-04 20:41:46
【问题描述】:

我有两个不同长度的数据框。 dfSamples(63012375 行)和 dfFixations(200000 行)。

dfSamples = pd.DataFrame({'tSample':[4, 6, 8, 10, 12, 14]})  
dfFixations = pd.DataFrame({'tStart':[4,12],'tEnd':[8,14]})

我想检查 dfSamples 中的每个值是否在 dfFixations 中给定的任意两个范围内,然后为该值分配一个标签。我发现了这个:Check if value in a dataframe is between two values in another dataframe,但循环解决方案非常慢,我无法让任何其他解决方案工作。

工作(但非常慢)示例:

labels = np.empty_like(dfSamples['tSample']).astype(np.chararray)
for i, fixation in dfFix.iterrows():
    log_range = dfSamples['tSample'].between(fixation['tStart'], fixation['tEnd'])
    labels[log_range] = 'fixation'
labels[labels != 'fixation'] = 'no_fixation'
dfSamples['labels'] = labels

按照这个例子:Performance of Pandas apply vs np.vectorize to create new column from existing columns 我试图向量化它但没有成功。

def check_range(samples, tstart, tend):
    log_range = (samples > tstart) & (samples < tend)
    return log_range
fixations = list(map(check_range, dfSamples['tSample'], dfFix['tStart'], dfFix['tEnd']))

不胜感激!

【问题讨论】:

    标签: python pandas dataframe vectorization


    【解决方案1】:

    IntervalIndex.from_arraysIntervalIndex.get_indexer 一起使用,如果不匹配则返回-1,因此检查并在numpy.where 中设置输出:

    i = pd.IntervalIndex.from_arrays(dfFixations['tStart'],
                                     dfFixations['tEnd'], 
                                     closed="both")
    pos = i.get_indexer(dfSamples['tSample'])
    dfSamples['labels'] = np.where(pos != -1, "fixation", "no_fixation")
    
    print (dfSamples)
       tSample       labels
    0        4     fixation
    1        6     fixation
    2        8     fixation
    3       10  no_fixation
    4       12     fixation
    5       14     fixation
    

    性能:在理想的nice sorted不重叠数据中,实际应该性能不同,最好测试一下。

    dfSamples = pd.DataFrame({'tSample':range(10000)})  
    dfFixations = pd.DataFrame({'tStart':range(0, 10000, 5),'tEnd':range(2, 10000, 5)})
        
    
    
    In [165]: %%timeit
         ...: labels = np.empty_like(dfSamples['tSample']).astype(np.chararray)
         ...: for i, fixation in dfFixations.iterrows():
         ...:     log_range = dfSamples['tSample'].between(fixation['tStart'], fixation['tEnd'])
         ...:     labels[log_range] = 'fixation'
         ...: labels[labels != 'fixation'] = 'no_fixation'
         ...: dfSamples['labels'] = labels
         ...: 
         ...: 
    1.25 s ± 52.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    
    In [168]: %%timeit
         ...: ii = pd.IntervalIndex.from_arrays(dfFixations['tStart'], dfFixations['tEnd'], closed="both")
         ...: dfSamples["labels1"] =  np.where(dfSamples["tSample"].apply(ii.contains).apply(any), "fixation", "no_fixation")
         ...: 
    315 ms ± 18.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    
    In [170]: %%timeit
         ...: ii = pd.IntervalIndex.from_arrays(dfFixations['tStart'], dfFixations['tEnd'], closed="both")
         ...: contained = np.logical_or.reduce(piso.contains(ii, dfSamples["tSample"], include_index=False), axis=0)
         ...: dfSamples["labels1"] = np.where(contained, "fixation", "no_fixation")
         ...: 
    82.4 ms ± 213 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    In [166]: %%timeit
         ...: s = pd.IntervalIndex.from_arrays(dfFixations['tStart'], dfFixations['tEnd'], closed="both")
         ...: pos = s.get_indexer(dfSamples['tSample'])
         ...: dfSamples['labels'] = np.where(pos != -1, "fixation", "no_fixation")
         ...: 
    27.8 ms ± 1.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    【讨论】:

    • 很好的答案!对于我的数据集,这比我的解决方案快 10 倍。谢谢!
    • 嗨,我收到错误消息“InvalidIndexError:无法处理重叠索引;使用 IntervalIndex.get_indexer_non_unique”,即使我的间隔没有重叠。如果我使用 [ii.overlaps(x) for x in ii] 只有对角线是 True
    • @heldm - 嗯,也许至少有一个重叠值。另一个解决方案也不起作用?
    • 再次正确。谢谢!
    【解决方案2】:

    设置

    dfSamples = pd.DataFrame({'tSample':[4, 6, 8, 10, 12, 14]})  
    dfFixations = pd.DataFrame({'tStart':[4,12],'tEnd':[8,14]})
    

    解决方案

    从起点和终点创建区间索引

    ii = pd.IntervalIndex.from_arrays(dfFixations['tStart'], dfFixations['tEnd'], closed="both")
    

    ii.contains 是一种检查点是否包含在区间索引中的每个区间中的方法,例如

    dfSamples["tSample"].apply(ii.contains)
    

    给予

    0     [True, False]
    1     [True, False]
    2     [True, False]
    3    [False, False]
    4     [False, True]
    5     [False, True]
    Name: tSample, dtype: object
    

    我们将利用这个结果,将any 函数应用于每个元素(一个列表)以获得布尔值的pandas.Series,然后我们可以将其与numpy.where 一起使用

    dfSamples["labels"] =  np.where(dfSamples["tSample"].apply(ii.contains).apply(any), "fixation", "no_fixation")
    

    结果

       tSample       labels
    0        4     fixation
    1        6     fixation
    2        8  no_fixation
    3       10  no_fixation
    4       12     fixation
    5       14  no_fixation
    

    编辑:更快的版本

    使用piso v0.6.0

    import piso
    import numpy as np
    
    ii = pd.IntervalIndex.from_arrays(dfFixations['tStart'], dfFixations['tEnd'], closed="both")
    contained = np.logical_or.reduce(piso.contains(ii, dfSamples["tSample"], include_index=False), axis=0)
    dfSamples["labels"] = np.where(contained, "fixation", "no_fixation")
    

    这将在与@jezrael 的解决方案类似的时间内运行,但是它可以处理间隔重叠的情况,例如

    dfFixations = pd.DataFrame({'tStart':[4,5,12],'tEnd':[8,9,14]})
    

    【讨论】:

    • 感谢您的回答!不幸的是,这对我来说似乎比 interrows 还要慢。比我上面的解决方案花费了大约 5 倍的时间
    • 我可能会为您提供更快的解决方案,但我需要知道您的间隔是如何“关闭”的。在您的第一个解决方案中,您的间隔在两端都关闭,而在您的第二个解决方案中,它们都没有关闭。
    • 会很棒!在这种情况下,最快的将是最好的。最好只有 10 人获得标签“no_fixation”。
    猜你喜欢
    • 2020-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多