Python:如何检查值列表是否包含在一个范围内

Python: how to check if a list of values is contained within a range

我正在处理一个结构为数据框的值列表,我想将此列表的每个值与另一个数据框(有点像下面的这个)进行比较:

Name    Start    End
Blue    10       28
Red     23       25
Green   89       107
Purple  168      216
Yellow  21       40

现在假设值列表是这样的:

Name    Value
W       37
X       176
Y       43
Z       96

对于“值”列中的每个元素,我想检查该值是否包含在第一个数据帧的每个“开始”-“结束”范围内,并将其添加到列表中(即 contained = []).在示例中 W (37) 包含在 Yellow 中, X (176) 包含在 Purple 中, Z (96) 包含在 Green 中,而 Y 不匹配所以它将被排除(或添加到另一个列表 not_contained = [])。 我怎样才能做到这一点?谢谢大家

因为在 sql 中的运算符连接之间没有很好的选择,或者您可以在临时键上交叉连接它们然后过滤结果。

df['key'] = 0
df2['key'] = 0

df.merge(df2, on='key', how='outer').query('Value >= Start & Value <= End').pop('Name_y').tolist()

作为松散的解决方案,您可以遍历一行并匹配值。我已经使用上述方法创建了一个解决方案。 将 pandas 导入为 pd

df = pd.DataFrame({'name': ['Blue', 'Red', 'Green', 'Purple', 'Yellow'],
                   'start':[10,23,89,168,21],
                   'end':[28,25,107,216,40]})

df2 = pd.DataFrame({'name':['W','X','Y','Z'],
                    'value':[37,176,43,96]})

contained=[]
not_contained = []

def checking(val):
    # iterate over df rows
    for index, row in df.iterrows():
        if val >= row['start'] and val <= row['end']:
            # if value is found, append to contained list and return
            contained.append(val)
            return True
    # if value is not found, append it to not_contained list
    not_contained.append(val)

# apply function
df2['value'].apply(checking)

print("contained: ",contained)
print("not_contained: ",not_contained)

输出:

contained:  [37, 176, 96]
not_contained:  [43]

您可以将数据框变成字典,然后查找系列中的每一项。

import pandas as pd

ser = pd.Series(index=list("WXYZ"), data=[37, 176, 43, 96], name="Value")

df = pd.DataFrame(
    {
        "Start": [10, 23, 89, 168, 21],
        "Name": ["Blue", "Red", "Green", "Purple", "Yellow"],
        "End": [28, 25, 107, 216, 40],
    }
)
df_dict = df.set_index(["Start", "End"])["Name"].to_dict()

ser.apply(
    lambda x: next(
        (color for bounds, color in df_dict.items() if x in range(*bounds)), None
    )
)

这给出了

W    Yellow
X    Purple
Y      None
Z     Green
Name: Value, dtype: object

我们的想法是 df_dict 一个以键为边界的字典:

{(10, 28): 'Blue',
 (23, 25): 'Red',
 (89, 107): 'Green',
 (168, 216): 'Purple',
 (21, 40): 'Yellow'}

然后循环遍历你的系列,询问每个元素 x 是否在边界之间,使用

`

7 in range(3,5)
# False

7 in range(3,10)
# True

您可以使用 conditional_join from pyjanitor 模拟范围连接,如 SQL -> 请注意,这是在 dev:

# pip install git+https://github.com/pyjanitor-devs/pyjanitor.git
import janitor
import pandas as pd

result = (df2.select_columns('Value')
             .conditional_join(df1.select_columns('Start', 'End'), 
                               ('Value', 'Start', '>='), 
                               ('Value', 'End', '<='), 
                               how = 'left')
           )
 
   Value  Start    End
0     37   21.0   40.0
1    176  168.0  216.0
2     43    NaN    NaN
3     96   89.0  107.0

根据 StartEnd 中的空值过滤到 containednot_contained:

contained = result.Value[result.Start.notna()].to_list()
not_contained = result.Value[~result.Start.notna()].to_list()

print(contained)
[37, 176, 96]

print(not_contained)

但在我看来,这有点矫枉过正,而且效率不高;你不需要数据框,你只需要一些列表;一种更简单的方法是使用 Pandas' 间隔,再加上 for 循环;对于大型数据帧,我希望这会更有效率:

# create intervalindex
intervals = pd.IntervalIndex.from_arrays(df1.Start, df1.End, closed = 'both')

# We could have used pandas get_indexer, 
# however, the intervals are overlapping,
# and get_indexer works only with non-overlapping/unique indexes

contained = []
not_contained = []

for _, value in df2.Value.items():
    if intervals.contains(value).any():
        contained.append(value)
    else:
        not_contained.append(value)

print(contained)
[37, 176, 96]

print(not_contained)
[43]