web-dev-qa-db-fra.com

Comment puis-je trouver les valeurs les plus proches dans une série Pandas à un numéro d'entrée?

J'ai vu:

Ceux-ci concernent Vanilla python et non les pandas.

Si j'ai la série:

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

Et j'entre 3, comment puis-je trouver (efficacement)?

  1. L'indice de 3 s'il se trouve dans la série
  2. L'index de la valeur en dessous et au dessus de 3 s'il ne se trouve pas dans la série.

C'est à dire. Avec la série ci-dessus {1,6,4,5,2} et l'entrée 3, je devrais obtenir des valeurs (4,2) avec des index (2,4).

29
Steve

Vous pouvez utiliser argsort() comme

Dire, 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 est la trame de données avec 2 valeurs les plus proches.

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

Pour l'index,

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

Pour les valeurs,

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

Détail, pour la solution ci-dessus df était

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

Je recommande d'utiliser iloc en plus de la réponse de John Galt car cela fonctionnera même avec un index entier non trié, puisque . Ix regarde d'abord les étiquettes d'index

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

En plus de ne pas répondre complètement à la question, un autre inconvénient des autres algorithmes discutés ici est qu'ils doivent trier la liste entière. Il en résulte une complexité de ~ N log (N) .

Cependant, il est possible d'obtenir les mêmes résultats dans ~ N . Cette approche sépare la trame de données en deux sous-ensembles, un plus petit et un plus grand que la valeur souhaitée. Le voisin inférieur est supérieur à la valeur la plus élevée dans la trame de données plus petite et vice versa pour le voisin supérieur.

Cela donne l'extrait de code suivant:

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

Cette approche est similaire à l'utilisation de partition in pandas , ce qui peut être très utile lorsque vous traitez de grands ensembles de données et la complexité devient un problème.


La comparaison des deux stratégies montre que pour un grand N, la stratégie de partitionnement est en effet plus rapide. Pour les petits N, la stratégie de tri sera plus efficace, car elle est mise en œuvre à un niveau beaucoup plus faible. Il s'agit également d'une ligne unique, ce qui pourrait augmenter la lisibilité du code. Comparison of partitioning vs sorting

Le code pour répliquer ce tracé peut être vu ci-dessous:

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')
5
Ivo Merchiers

Si votre série est déjà triée, vous pouvez utiliser quelque chose comme ça.

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.
2
kztd

Si la série est déjà triée, une méthode efficace pour trouver les index consiste à utiliser bissect . Un exemple:

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

Donc pour le problème cité dans la question, sachant que la colonne "col" du dataframe "df" est triée:

from bisect import bisect_right, bisect_left
def get_closests(df, col, val):
    lower_idx = bisect_right(df[col].values, val)
    higher_idx = bisect_left(df[col].values, val)
if higher_idx == lower_idx:
    return lower_idx
else: 
    return lower_idx, higher_idx

Il est assez efficace de trouver l'index de la valeur spécifique "val" dans la colonne de trame de données "col", ou ses voisins les plus proches, mais cela nécessite que la liste soit triée.

1
Maxence Bouvier