web-dev-qa-db-de.com

Wie finde ich die Werte, die einer eingegebenen Zahl in einer Pandas) -Reihe am nächsten kommen?

Ich habe gesehen:

Diese beziehen sich auf Vanilla python und nicht auf Pandas.

Wenn ich die Serie habe:

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

Und ich gebe 3 ein, wie kann ich (effizient) finden?

  1. Der Index von 3, wenn er in der Reihe gefunden wird
  2. Der Index des Werts unter und über 3, wenn er nicht in der Reihe gefunden wird.

Dh Mit der obigen Reihe {1,6,4,5,2} und Eingabe 3 sollte ich Werte (4,2) mit Indizes (2,4) erhalten.

29
Steve

Sie könnten argsort() like verwenden

Sagen, 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 ist der Datenrahmen mit 2 nächsten Werten.

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

Für den Index

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

Für Werte,

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

Detail, für die obige Lösung war df

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

Ich empfehle die Verwendung von iloc zusätzlich zu John Galts Antwort, da dies auch bei nicht sortiertem Ganzzahlindex funktioniert, da . Ix zuerst die Indexbezeichnungen betrachtet

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

Abgesehen davon, dass die Frage nicht vollständig beantwortet wurde, besteht ein zusätzlicher Nachteil der anderen hier diskutierten Algorithmen darin, dass sie die gesamte Liste sortieren müssen. Dies ergibt eine Komplexität von ~ N log (N) .

Es ist jedoch möglich, die gleichen Ergebnisse in ~ N zu erzielen. Dieser Ansatz trennt den Datenrahmen in zwei Teilmengen, eine kleinere und eine größere als der gewünschte Wert. Der untere Nachbar ist der größte Wert im kleineren Datenrahmen und umgekehrt für den oberen Nachbarn.

Dies ergibt das folgende Code-Snippet:

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

Dieser Ansatz ähnelt der Verwendung von Partition in Pandas , was beim Umgang mit großen Datenmengen und bei Problemen mit der Komplexität sehr hilfreich sein kann.


Der Vergleich beider Strategien zeigt, dass die Partitionierungsstrategie für große N tatsächlich schneller ist. Für kleine N ist die Sortierstrategie effizienter, da sie auf einer viel niedrigeren Ebene implementiert wird. Es ist auch ein Einzeiler, der die Lesbarkeit des Codes verbessern kann. Comparison of partitioning vs sorting

Der Code zum Replizieren dieses Diagramms ist unten zu sehen:

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

Wenn Ihre Serie bereits sortiert ist, können Sie so etwas verwenden.

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

Wenn die Reihe bereits sortiert ist, können Sie die Indizes effizient mit bisect ermitteln. Ein Beispiel:

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

Also für das in der Frage genannte Problem, wenn man bedenkt, dass die Spalte "col" des Datenrahmens "df" sortiert ist:

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

Es ist sehr effizient, den Index des bestimmten Werts "val" in der Datenrahmenspalte "col" oder den nächsten Nachbarn zu finden, erfordert jedoch, dass die Liste sortiert wird.

1
Maxence Bouvier