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)?
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).
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
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]]
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.
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')
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.
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.