Как найти ближайшие значения в серии Pandas для ввода номера?

Я видел:

Они относятся к ванильному питону, а не к pandas.

Если у меня есть ряд:

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

И я ввожу 3, , как я могу (эффективно) найти?

  • Индекс 3, если он найден в серии
  • Индекс значения ниже и выше 3, если он не найден в серии.

Т.е. С приведенными выше рядами {1,6,4,5,2} и ввода 3 я должен получить значения (4,2) с индексами (2,4).

Ответ 1

Вы можете использовать argsort() как

Скажем, input = 3

In [198]: input = 3

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

df_sort - это кадр данных с двумя ближайшими значениями.

In [200]: df_sort = df.ix[(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

Ответ 2

Я рекомендую использовать iloc в дополнение к ответу Джона Галта, поскольку это будет работать даже с несортированным целочисленным индексом, поскольку .ix сначала просматривает метки индекса

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

Ответ 3

Помимо не полного ответа на этот вопрос, дополнительным недостатком других алгоритмов, обсуждаемых здесь, является то, что они должны сортировать весь список. Это приводит к сложности ~ N log (N).

Тем не менее, можно достичь тех же результатов в ~ N. Этот подход разделяет фрейм данных на два подмножества: одно меньше, а другое больше желаемого значения. Нижний сосед имеет наибольшее значение в меньшем кадре данных, и наоборот для верхнего соседа.

Это дает следующий фрагмент кода:

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

Этот подход аналогичен использованию раздела в пандах, который может быть очень полезен при работе с большими наборами данных, когда возникает сложность.


Сравнение обеих стратегий показывает, что для больших N стратегия разделения действительно быстрее. Для малого N стратегия сортировки будет более эффективной, поскольку она реализована на гораздо более низком уровне. Это также однострочник, который может улучшить читаемость кода. Comparison of partitioning vs sorting

Код для воспроизведения этого сюжета можно увидеть ниже:

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

Ответ 4

Если ваша серия уже отсортирована, вы можете использовать что-то вроде этого.

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.

Ответ 5

Если ряды уже отсортированы, эффективный метод поиска индексов - использование функций bisect. Пример:

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

Давайте рассмотрим, что столбец col кадра данных df отсортирован.

  • В случае, когда значение 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

Алгоритмы Bisect очень эффективны для нахождения индекса определенного значения "val" в столбце "col" блока данных или его ближайших соседей, но для этого требуется сортировка списка.