如何在Pandas系列中找到与输入数字最接近的值?

jfgube3f  于 2023-03-06  发布在  其他
关注(0)|答案(9)|浏览(204)

我已经看到:

这些与香草蟒有关,而不是Pandas。
如果我有这个系列:

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

我输入3,我如何(有效地)找到?
1.索引3(如果在序列中找到)
1.小于和大于3的值的索引(如果在序列中找不到)。
也就是说,使用上面的序列{1,6,4,5,2},输入3,我应该得到索引为(2,4)的值(4,2)。

vvppvyoh

vvppvyoh1#

您可以像这样使用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个最接近值的 Dataframe 。

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
5kgi1eie

5kgi1eie2#

除了不能完全回答这个问题之外,这里讨论的其他算法还有一个额外的缺点,那就是它们必须对整个列表进行排序,这导致了**~N log(N)的复杂度。
然而,在
~N**中也可能达到相同的结果。这种方法将 Dataframe 分成两个子集,一个小于所需值,一个大于所需值。较低的相邻 Dataframe 比较小 Dataframe 中的最大值大,而较高的相邻 Dataframe 则相反。
这将给出以下代码片段:

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,当处理大型数据集且复杂性成为问题时,partition in pandas非常有用。
比较这两种策略可以看出,对于大N,分区策略确实更快。对于小N,排序策略将更有效,因为它是在更低的级别实现的。它还是一行程序,这可能会提高代码可读性。x1c 0d1x
复制此图的代码如下所示:

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')
mzsu5hc0

mzsu5hc03#

我建议除了JohnGalt的答案之外,还使用iloc,因为.ix首先查看索引标签,所以即使是未排序的整数索引也可以使用iloc

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

ymdaylpp4#

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

idx = bisect_left(df['num'].values, 3)
    • 让我们考虑** Dataframe df的列col被排序
  • 如果值val在列中,则bisect_left将返回列表中值的精确索引,bisect_right将返回下一个位置的索引。
  • 如果值不在列表中,bisect_leftbisect_right将返回相同的索引:要插入值以保持列表排序的位置。

因此,为了回答这个问题,下面的代码给出了valcol中的索引(如果找到的话),以及最接近的值的索引(否则)。
x一个一个一个一个x一个一个二个x
二分算法在查找 Dataframe 列"col"中特定值"val"的索引或其最近邻居时非常有效,但它需要对列表进行排序。

wswtfjt7

wswtfjt75#

如果您的序列已经排序,您可以使用类似下面的代码。

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.
jhiyze9q

jhiyze9q6#

可以使用numpy.searchsorted。如果搜索列尚未排序,则可以创建一个已排序的DataFrame,并使用pandas.argsort记住它们之间的Map。(如果计划多次查找最接近的值,则此方法优于上述方法。)
排序后,找到与输入最接近的值,如下所示:

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

valLeft = df['column'][indLeft]
valRight = df['column'][indRight]
cngwdvgl

cngwdvgl7#

我发现解决这类问题最直观的方法是使用@ivo-merchiers建议的划分方法,但使用nminimum和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
j8yoct9x

j8yoct9x8#

如果您需要在'num'列中找到与obj_num最接近的值,并且在有多个选择的情况下,您可以基于'num'以外的其他列的值来选择最佳出现,例如第二列'num2'
为此,我建议创建一个新列'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, :]}')

下面的函数使用目标值和列的dict来完成这项工作(它考虑用于排序的列的顺序):

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'
    }
)
dced5bon

dced5bon9#

这里有很多答案,很多都很不错。没有一个答案被接受,@Zero的答案是目前评分最高的。另一个答案指出,当索引还没有排序时,它不起作用,但他/她推荐了一个看起来不赞成的解决方案。
我发现可以按以下方式对值本身使用numpy版本的argsort(),即使索引没有排序也能正常工作:

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

请看Zero的回答。

相关问题