536 Stimmen

Python Pandas: Index der Zeilen erhalten, in denen eine Spalte einen bestimmten Wert hat

Mit einem DataFrame mit einer Spalte "BoolCol" möchten wir die Indizes des DataFrames finden, in denen die Werte für "BoolCol" == True sind

Derzeit habe ich die iterative Methode, die perfekt funktioniert:

for i in range(100,3000):
    if df.iloc[i]['BoolCol']== True:
         print i,df.iloc[i]['BoolCol']

Aber das ist nicht der richtige Weg, es mit pandas zu tun. Nach einiger Recherche verwende ich derzeit diesen Code:

df[df['BoolCol'] == True].index.tolist()

Dieser gibt mir eine Liste von Indizes, aber sie passen nicht, wenn ich sie überprüfe, indem ich Folgendes tue:

df.iloc[i]['BoolCol']

Das Ergebnis ist tatsächlich False!!

Wie wäre der korrekte Weg, dies mit pandas zu tun?

2voto

not a robot Punkte 3525

Ein weiterer Methode ist die Verwendung von pipe() um das Indizieren des Index von BoolCol zu verketten. In Bezug auf die Leistung ist es genauso effizient wie das kanonische Indizieren mit [].1

df['BoolCol'].pipe(lambda x: x.index[x])

Dies ist besonders nützlich, wenn BoolCol tatsächlich das Ergebnis mehrerer Vergleiche ist und Sie die Methodenverknüpfung verwenden möchten, um alle Methoden in einer Pipeline zu platzieren.

Zum Beispiel, wenn Sie die Zeilenindizes erhalten möchten, bei denen der Wert von NumCol größer als 0,5 ist, der Wert von BoolCol True ist und das Produkt aus den Werten von NumCol und BoolCol größer als 0 ist, können Sie dies tun, indem Sie einen Ausdruck über eval() evaluieren und dann pipe() auf das Ergebnis aufrufen, um das Indizieren der Indizes durchzuführen.2

df.eval("NumCol > 0.5 and BoolCol and NumCol * BoolCol >0").pipe(lambda x: x.index[x])

1: Der folgende Benchmark verwendete ein DataFrame mit 20 Mio. Zeilen (im Durchschnitt wurden die Hälfte der Zeilen gefiltert) und holte ihre Indizes ab. Die Methodenverknüpfung über pipe() funktioniert im Vergleich zu anderen effizienten Optionen sehr gut.

n = 20_000_000
df = pd.DataFrame({'NumCol': np.random.rand(n).astype('float16'), 
                   'BoolCol': np.random.default_rng().choice([True, False], size=n)})

%timeit df.index[df['BoolCol']]
# 181 ms ± 2.47 ms pro Durchlauf (Mittelwert ± Standardabweichung von 10 Durchläufen, 1000 Schleifen pro Durchlauf)

%timeit df['BoolCol'].pipe(lambda x: x.index[x])
# 181 ms ± 1.08 ms pro Durchlauf (Mittelwert ± Standardabweichung von 10 Durchläufen, 1000 Schleifen pro Durchlauf)

%timeit df['BoolCol'].loc[lambda x: x].index
# 297 ms ± 7.15 ms pro Durchlauf (Mittelwert ± Standardabweichung von 10 Durchläufen, 1000 Schleifen pro Durchlauf)

2: Für ein 20 Mio. Zeilen DataFrame, das auf die gleiche Weise wie im 1 konstruiert wurde, werden Sie feststellen, dass die hier vorgeschlagene Methode die schnellste Option ist. Sie funktioniert besser als die Bit-Operator-Verkettung, weil eval() aufgrund seiner Struktur mehrere Operationen schneller auf einem großen DataFrame durchführt als vektorisierte Python-Operationen und speichereffizienter als query() ist, weil im Gegensatz zu query(), eval().pipe(...) keine Kopie des geschnittenen DataFrames erstellen muss, um dessen Index zu erhalten.

1voto

Carson Punkte 3411

Ich habe diese Frage erweitert, nämlich wie man die Zeile, Spalte und Wert aller übereinstimmenden Werte erhält?

Hier ist die Lösung:

import pandas as pd
import numpy as np

def search_coordinate(df_data: pd.DataFrame, search_set: set) -> list:
    nda_values = df_data.values
    tuple_index = np.where(np.isin(nda_values, [e for e in search_set]))
    return [(row, col, nda_values[row][col]) for row, col in zip(tuple_index[0], tuple_index[1])]

if __name__ == '__main__':
    test_datas = [['cat', 'dog', ''],
                  ['goldfish', '', 'kitten'],
                  ['Puppy', 'hamster', 'mouse']
                  ]
    df_data = pd.DataFrame(test_datas)
    print(df_data)
    result_list = search_coordinate(df_data, {'dog', 'Puppy'})
    print(f"\n\n{'row':<4} {'col':<4} {'name':>10}")
    [print(f"{row:<4} {col:<4} {name:>10}") for row, col, name in result_list]

Ausgabe:

          0        1       2
0       cat      dog        
1  goldfish           kitten
2     Puppy  hamster   mouse

row  col        name
0    1           dog
2    0         Puppy

1voto

Für bekannte Indexkandidaten, die uns interessieren, kann auf schnellere Weise durch Nichtüberprüfen der gesamten Spalte wie folgt vorgegangen werden:

np.array(index_slice)[np.where(df.loc[index_slice]['column_name'] >= threshold)[0]]

Vollständiger Vergleich:

import pandas as pd
import numpy as np

index_slice = list(range(50,150)) # known index location for our inteterest
data = np.zeros(10000)
data[(index_slice)] = np.random.random(len(index_slice))

df = pd.DataFrame(
    {'column_name': data},
)

threshold = 0.5

%%timeit
np.array(index_slice)[np.where(df.loc[index_slice]['column_name'] >= threshold)[0]]
# 600 µs ± 1.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%%timeit
[i for i in index_slice if i in df.index[df['column_name'] >= threshold].tolist()]
# 22.5 ms ± 29.1 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

So funktioniert es:

# Generiere Boolean, die nur in der geschnittenen Spalte der Bedingung genügt
df.loc[index_slice]['column_name'] >= threshold

# Konvertiere Boolean in Index, beginnend von 0 und inkrementiere um 1
np.where(...)[0]

# Liste der zu schneidenden Indizes
np.array(index_slice)[...]

Hinweis: Es sollte beachtet werden, dass np.array(index_slice) aufgrund der Indexierung von np.where(...)[0] mit Beginnend von 0 und Inkrement um 1 nicht durch df.index ersetzt werden kann, aber Sie können etwas wie df.index[index_slice] machen. Und ich denke, dass sich der Aufwand nicht lohnt, wenn Sie es nur einmal mit einer kleinen Anzahl von Zeilen tun.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X