【问题标题】:How do I find the closest values in a Pandas series to an input number?如何在 Pandas 系列中找到与输入数字最接近的值?
【发布时间】:2015-07-18 16:23:58
【问题描述】:

我见过:

这些与香草蟒有关,而不是熊猫。

如果我有这个系列:

ix   num  
0    1
1    6
2    4
3    5
4    2

我输入 3,我怎样才能(有效地)找到?

  1. 如果在系列中找到,则为 3 的索引
  2. 如果在系列中没有找到低于和高于 3 的值的索引。

即。使用上述系列 {1,6,4,5,2} 和输入 3,我应该得到具有索引 (2,4) 的值 (4,2)。

【问题讨论】:

  • 请注意,标题询问的是最接近的值,但问题文本本身询问的是(最接近的)下限和上限。从 cmets 到其中一个答案可以看出,这些并不相同(这里的大多数答案似乎都回答了标题,而不是问题文本)。

标签: python pandas dataframe ranking


【解决方案1】:

你可以使用argsort() 喜欢

说,input = 3

In [198]: input = 3

In [199]: df.iloc[(df['num']-input).abs().argsort()[:2]]
Out[199]:
   num
2    4
4    2

df_sort 是具有 2 个最接近值的数据框。

In [200]: df_sort = df.iloc[(df['num']-input).abs().argsort()[:2]]

对于索引,

In [201]: df_sort.index.tolist()
Out[201]: [2, 4]

对于价值观,

In [202]: df_sort['num'].tolist()
Out[202]: [4, 2]

详情,对于上述解决方案df是

In [197]: df
Out[197]:
   num
0    1
1    6
2    4
3    5
4    2

【讨论】:

  • 这是找到最接近的下方和上方,还是仅找到最接近的两个?
  • 下面和上面是什么意思?最接近的值是通过它们与给定输入之间的绝对差来选择的。
  • 我需要找到 a) 上面最近的号码,b) 下面最接近的号码。因此,绝对差异不会在所有情况下都实现这一点。
  • 这给出了错误的答案。我在一个更复杂的数据集上尝试了这个。您必须使用 .iloc 而不是 .ix 并且效果很好(请参阅@op1)
  • 这种方法不会有 N log(N) 的复杂度,因为您需要排序吗?这似乎不是非常有效,因为应该可以在 ~O(N) 中执行搜索?
【解决方案2】:

除了没有完全回答问题之外,这里讨论的其他算法的一个额外缺点是它们必须对整个列表进行排序。这导致 ~N log(N) 的复杂度。

但是,在 ~N 中可以达到相同的结果。这种方法将数据帧分成两个子集,一个比期望值小,一个比期望值大。较低的邻居大于较小数据帧中的最大值,反之亦然。

这给出了以下代码sn-p:

def find_neighbours(value, df, colname):
    exactmatch = df[df[colname] == value]
    if not exactmatch.empty:
        return exactmatch.index
    else:
        lowerneighbour_ind = df[df[colname] < value][colname].idxmax()
        upperneighbour_ind = df[df[colname] > value][colname].idxmin()
        return [lowerneighbour_ind, upperneighbour_ind] 

这种方法类似于使用partition in pandas,在处理大型数据集和复杂性成为问题时非常有用。


比较这两种策略可以看出,对于大的 N,分区策略确实更快。对于较小的 N,排序策略将更有效,因为它的实现层次要低得多。它也是单行的,可能会增加代码的可读性。

复制此图的代码如下所示:

from matplotlib import pyplot as plt
import pandas
import numpy
import timeit

value=3
sizes=numpy.logspace(2, 5, num=50, dtype=int)

sort_results, partition_results=[],[]
for size in sizes:
    df=pandas.DataFrame({"num":100*numpy.random.random(size)})
    
    sort_results.append(timeit.Timer("df.iloc[(df['num']-value).abs().argsort()[:2]].index",
                                         globals={'find_neighbours':find_neighbours, 'df':df,'value':value}).autorange())
    partition_results.append(timeit.Timer('find_neighbours(df,value)',
                                          globals={'find_neighbours':find_neighbours, 'df':df,'value':value}).autorange())
    
sort_time=[time/amount for amount,time in sort_results]
partition_time=[time/amount for amount,time in partition_results]

plt.plot(sizes, sort_time)
plt.plot(sizes, partition_time)
plt.legend(['Sorting','Partitioning'])
plt.title('Comparison of strategies')
plt.xlabel('Size of Dataframe')
plt.ylabel('Time in s')
plt.savefig('speed_comparison.png')

【讨论】:

  • 我喜欢这个主意!但是,在我看来,如果索引不是 monotonic,lower..._id 和 upper..._id 很有可能不会彼此相邻;这是其他算法试图通过排序提供的。
  • @Joël 我想我不明白你的意思。它们必须彼此相邻,因为lower... 是小于value 的最大值,upper... 是大于小于的最小值。我看不出两者之间怎么会有另一个价值?还是你的意思是别的?
  • 我认为这个答案更正确,因为它给出了最接近的下限和上限,而不仅仅是两个最接近的值。它确实有两个(小)错误:第 3 行的缩进,以及使用了未知的traversed 变量(可能应该是value);因为我对错误有 90% 的把握,而不是 100%,所以我仍然有点犹豫要编辑和修复答案。
  • @ClaudiuCreanga 这取决于df 的大小。我添加了一个情节和一些代码来说明这种行为。
  • @Isaac 感谢您的建议,我批准了编辑。不过,这些 cmets 有助于理解您的编辑。
【解决方案3】:

我建议在 John Galt 的回答之外使用 iloc,因为即使使用未排序的整数索引,这也适用,因为 .ix 首先查看索引标签

df.iloc[(df['num']-input).abs().argsort()[:2]]

【讨论】:

【解决方案4】:

如果系列已经排序,查找索引的有效方法是使用bisect 函数。 一个例子:

idx = bisect_left(df['num'].values, 3)

让我们考虑数据框df的列col已排序。

  • 如果值val在列中,bisect_left 将返回列表中值的精确索引,并且 bisect_right 将返回下一个位置的索引。
  • 在值不在列表中的情况下,bisect_left 和bisect_right 将返回相同的索引: 插入值以保持列表排序。

因此,为了回答这个问题,下面的代码给出了val 在col 中的索引(如果找到的话),否则给出最接近值的索引。即使列表中的值不是唯一的,此解决方案也有效。

from bisect import bisect_left, bisect_right
def get_closests(df, col, val):
    lower_idx = bisect_left(df[col].values, val)
    higher_idx = bisect_right(df[col].values, val)
if higher_idx == lower_idx:      #val is not in the list
    return lower_idx - 1, lower_idx
else:                            #val is in the list
    return lower_idx

二等分算法在数据框列“col”或其最近邻居中查找特定值“val”的索引非常有效,但它需要对列表进行排序。

【讨论】:

    【解决方案5】:

    如果你的系列已经排序,你可以使用这样的东西。

    def closest(df, col, val, direction):
        n = len(df[df[col] <= val])
        if(direction < 0):
            n -= 1
        if(n < 0 or n >= len(df)):
            print('err - value outside range')
            return None
        return df.ix[n, col]    
    
    df = pd.DataFrame(pd.Series(range(0,10,2)), columns=['num'])
    for find in range(-1, 2):
        lc = closest(df, 'num', find, -1)
        hc = closest(df, 'num', find, 1)
        print('Closest to {} is {}, lower and {}, higher.'.format(find, lc, hc))
    
    
    df:     num
        0   0
        1   2
        2   4
        3   6
        4   8
    err - value outside range
    Closest to -1 is None, lower and 0, higher.
    Closest to 0 is 0, lower and 2, higher.
    Closest to 1 is 0, lower and 2, higher.
    

    【讨论】:

      【解决方案6】:

      您可以使用numpy.searchsorted。如果您的搜索列尚未排序,您可以创建一个已排序的 DataFrame,并使用pandas.argsort 记住它们之间的映射。 (如果您打算多次查找最接近的值,这比上述方法要好。)

      排序后,为您的输入找到最接近的值,如下所示:

      indLeft = np.searchsorted(df['column'], input, side='left')
      indRight = np.searchsorted(df['column'], input, side='right')
      
      valLeft = df['column'][indLeft]
      valRight = df['column'][indRight]
      

      【讨论】:

      • 如果输入与列中的元素不完全匹配,那么我认为 indLeft 和 indRight 是相等的。该请求是针对两个最接近的索引。
      【解决方案7】:

      我发现解决此类问题的最直观的方法是使用@ivo-merchiers 建议的分区方法,但使用 nsmallest 和 nlargest。除了处理未排序的序列之外,这种方法的一个好处是您可以通过将 k_matches 设置为大于 1 的数字轻松获得多个接近的值。

      import pandas as pd
      source = pd.Series([1,6,4,5,2])
      target = 3
      
      def find_closest_values(target, source, k_matches=1):
          k_above = source[source >= target].nsmallest(k_matches)
          k_below = source[source < target].nlargest(k_matches)
          k_all = pd.concat([k_below, k_above]).sort_values()
          return k_all
      
      find_closest_values(target, source, k_matches=1)
      

      输出:

      4    2
      2    4
      dtype: int64
      

      【讨论】:

        【解决方案8】:

        如果您需要在'num' 列中找到最接近obj_num 的值,并且如果有多个选择,您可以根据除'num' 之外的其他列的值选择最佳出现,例如第二列@987654324 @。

        为此,我建议创建一个新列'num_diff',然后使用sort_values。示例:我们要在'num' 列中选择最接近3 的值,如果出现很多,请在'num2' 列中选择最小值。代码如下:

        import pandas as pd
        
        obj_num = 3
        df = pd.DataFrame({
            'num': [0, 1, 3, 3, 3, 4],
            'num2': [0, 0, 0, -1, 1, 0]
        })
        
        df_copy = df.loc[:, ['num', 'num2']].copy()
        df_copy['num_diff'] = (df['num']-obj_num).abs()
        df_copy.sort_values(
            by=['num_diff', 'num2'],
            axis=0,
            inplace=True
        )
        obj_num_idx = df_copy.index[0]
        
        print(f'Objective row: \n{df.loc[obj_num_idx, :]}')
        

        这是一个使用目标值和列的字典来完成这项工作的函数(它尊重用于排序的列的顺序):

        def colosest_row(df, obj):
            '''
            Sort df using specific columns given as obj keys.
            If a key has None value:
                sort column in ascending order.
            If a key has a float value:
                sort column from closest to farest value from obj[key] value.
        
            Arguments
            ---------
            df: pd.DataFrame
                contains at least obj keys in its columns.
            obj: dict
                dict of objective columns.
            
            Return
            ------
            index of closest row to obj
            '''
            df_copy = df.loc[:, [*obj]].copy()
        
            special_cols = []
            obj_cols = []
            for key in obj:
                if obj[key] is None:
                    obj_cols.append(key)
                else:
                    special_cols.append(key)
                    obj_cols.append(f'{key}_diff')
        
            for key in special_cols:
                df_copy[f'{key}_diff'] = (df[key]-obj[key]).abs()
        
            df_copy.sort_values(
                by=obj_cols,
                axis=0,
                ascending=True,
                inplace=True
            )
        
            return df_copy.index[0]
        
        obj_num_idx = colosest_row(
            df=df,
            obj={
                "num": obj_num,
                "num2": None  # Sort using also 'num2'
            }
        )
        

        【讨论】:

          【解决方案9】:

          这里有很多答案,其中很多都很好。没有被接受,@Zero 的回答目前评价最高。另一个答案指出,当索引尚未排序时它不起作用,但他/她推荐了一个似乎已弃用的解决方案。

          我发现我可以通过以下方式在值本身上使用 numpy 版本的 argsort(),即使索引未排序也可以:

          df.iloc[(df['num']-input).abs()..values.argsort()[:2]]
          

          有关上下文,请参阅 Zero 的答案。

          【讨论】:

          • 我认为你最好在这里使用 argmin()。
          猜你喜欢
          • 1970-01-01
          • 2022-11-28
          • 1970-01-01
          • 1970-01-01
          • 2012-11-14
          • 2021-10-06
          • 2016-01-13
          • 2020-10-14
          • 2021-06-03
          相关资源
          最近更新 更多