簡體   English   中英

如何在 Pandas 系列中找到與輸入數字最接近的值?

[英]How do I find the closest values in a Pandas series to an input number?

我見過:

這些與香草蟒蛇有關,而不是熊貓。

如果我有這個系列:

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

我輸入 3,我怎樣才能(有效地)找到?

  1. 如果在系列中找到,則索引為 3
  2. 如果在系列中找不到低於和高於 3 的值的索引。

IE。 使用上述系列 {1,6,4,5,2} 和輸入 3,我應該得到帶有索引 (2,4) 的值 (4,2)。

你可以像這樣使用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 個最接近值的數據df_sort

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

除了不能完全回答這個問題之外,這里討論的其他算法的一個額外缺點是它們必須對整個列表進行排序。 這導致~N log(N)的復雜性。

但是,可以在~N 中獲得相同的結果。 這種方法將數據幀分成兩個子集,一個比所需值小,一個大。 較低的鄰居小於較小數據幀中的最大值,反之亦然。

這給出了以下代碼片段:

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] 

這種方法類似於在 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')

除了 John Galt 的答案之外,我還建議使用iloc ,因為即使使用未排序的整數索引也可以使用iloc ,因為.ix首先查看索引標簽

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

如果系列已經排序,查找索引的有效方法是使用二等分函數。 一個例子:

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

讓我們考慮一下數據框df的列col已排序

  • 在值val在列中的情況下, bisect_left將返回列表中值的精確索引, bisect_right將返回下一個位置的索引。
  • 在值不在列表中的情況下, bisect_leftbisect_right將返回相同的索引:插入值以保持列表排序的位置。

因此,為了回答這個問題,以下代碼給出colval的索引(如果找到),否則給出最接近值的索引。 即使列表中的值不唯一,此解決方案也能工作。

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”的索引,但它需要對列表進行排序。

如果你的系列已經排序,你可以使用這樣的東西。

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.

您可以使用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]

我發現解決這類問題的最直觀的方法是使用@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

這里有很多答案,其中許多都非常好。 沒有人被接受,@Zero 的回答目前評價最高。 另一個答案指出,當索引尚未排序時它不起作用,但他/她推薦了一個似乎已棄用的解決方案。

我發現我可以按以下方式在值本身上使用argsort()的 numpy 版本,即使索引未排序也能正常工作:

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

有關上下文,請參閱 Zero 的回答。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM