Hapus baris dari panda DataFrame berdasarkan pada ekspresi kondisional yang melibatkan len (string) yang memberikan KeyError

303

Saya memiliki DataFrame panda dan saya ingin menghapus baris darinya di mana panjang string dalam kolom tertentu lebih besar dari 2.

Saya berharap dapat melakukan ini (per jawaban ini ):

df[(len(df['column name']) < 2)]

tapi saya baru saja mendapatkan kesalahan:

KeyError: u'no item named False'

Apa yang saya lakukan salah?

(Catatan: Saya tahu saya bisa gunakan df.dropna()untuk menghilangkan baris yang berisi apa pun NaN, tapi saya tidak melihat cara menghapus baris berdasarkan ekspresi kondisional.)

sjs
sumber

Jawaban:

168

Ketika Anda melakukannya, len(df['column name'])Anda hanya mendapatkan satu nomor, yaitu jumlah baris dalam DataFrame (yaitu, panjang kolom itu sendiri). Jika Anda ingin menerapkan lenke setiap elemen di kolom, gunakan df['column name'].map(len). Jadi coba

df[df['column name'].map(len) < 2]
BrenBarn
sumber
3
Saya datang dengan cara menggunakan daftar pemahaman: df[[(len(x) < 2) for x in df['column name']]]tetapi Anda jauh lebih baik. Terima kasih atas bantuan Anda!
sjs
13
Jika seseorang membutuhkan perbandingan yang lebih kompleks, lambda selalu dapat digunakan. df[df['column name'].map(lambda x: str(x)!=".")]
4lberto
1
Untuk beberapa alasan, tidak ada opsi lain yang berfungsi untuk saya, kecuali yang diposkan oleh @ 4lberto. Saya menggunakan pandas 0.23.4python 3.6
goelakash
1
Saya akan menambahkan .copy()di akhir, jika Anda ingin mengedit dataframe ini nanti (misalnya, menugaskan kolom baru akan menaikkan "Nilai sedang mencoba untuk ditetapkan pada salinan sepotong dari dataFrame" peringatan.
PlasmaBinturong
807

Untuk langsung menjawab judul asli pertanyaan ini "Cara menghapus baris dari panda DataFrame berdasarkan ekspresi kondisional" (yang saya pahami belum tentu merupakan masalah OP tetapi dapat membantu pengguna lain menemukan pertanyaan ini) salah satu cara untuk melakukannya adalah menggunakan yang penurunan metode:

df = df.drop(some labels)

df = df.drop(df[<some boolean condition>].index)

Contoh

Untuk menghapus semua baris dengan 'skor' kolom <50:

df = df.drop(df[df.score < 50].index)

Di tempat versi (seperti yang ditunjukkan dalam komentar)

df.drop(df[df.score < 50].index, inplace=True)

Berbagai kondisi

(lihat Boolean Indexing )

Operator adalah: |untuk or, &untuk and, dan ~untuk not. Ini harus dikelompokkan dengan menggunakan tanda kurung.

Untuk menghapus semua baris dengan 'skor' kolom <50 dan> 20

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)

Pengguna
sumber
32
Saya hanya ingin berkomentar, bahwa fungsi drop mendukung penggantian inplace. Yaitu,. solusi Anda sama dengan df.drop (df [df.score <50] .index, inplace = True). Meskipun demikian tidak tahu trik "indeks". Banyak membantu saya
Quickbeam2k1
9
Hanya ingin menunjukkan bahwa sebelum Anda menggunakan trik indeks ini, Anda perlu memastikan bahwa nilai indeks Anda unik (atau panggilan reset_index()). Saya menemukan ini dengan cara yang sulit ketika banyak baris dijatuhkan dari dataframe saya.
Jay
3
bagaimana cara menghapus semua baris di mana jenis kolom str? Saya hanya ingin menyimpan jenis kolom daftar. Saya sudah mencoba test = df.drop(df[df['col1'].dtype == str].index)tetapi saya mendapatkan kesalahan KeyError: False saya juga mencoba df.drop(df[df.col1.dtype == str].index)dan df.drop(df[type(df.cleaned_norm_email) == str].index)tetapi tidak ada yang berhasil? Adakah yang bisa menyarankan. Terima kasih! @ Pengguna
PyRsquared
1
Ini adalah pertanyaan lama, tetapi ... @ ikan yang ditantang secara air jauh lebih cepat daripada yang ini. Perhatikan bahwa Anda menghitung df[(df.score < 50) & (df.score > 20)]sebagai bagian dari jawaban Anda. Jika Anda membalikkan ini, df = df[(df.score >= 50) | (df.score <= 20)]Anda akan mendapatkan jawaban Anda jauh lebih cepat.
Roobie Nuby
1
@RoobieNuby - kondisinya tidak sama.
Nguai al
106

Anda dapat menetapkan DataFrameuntuk versi yang disaring itu sendiri:

df = df[df.score > 50]

Ini lebih cepat dari drop:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Kabard
sumber
Bagaimana cara memeriksa beberapa kolom menggunakan atau ketentuan?
Piyush S. Wanare
9

Saya akan memperluas pada solusi generik @ Pengguna untuk memberikan dropalternatif gratis. Ini untuk orang-orang yang diarahkan di sini berdasarkan pada judul pertanyaan (bukan masalah OP)

Katakanlah Anda ingin menghapus semua baris dengan nilai negatif. Satu solusi liner adalah: -

df = df[(df > 0).all(axis=1)]

Langkah demi langkah Penjelasan: -

Mari kita buat kerangka data distribusi normal 5x5 acak

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

Biarkan kondisinya menghapus negatif. Boolean df memuaskan kondisi: -

df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

Serangkaian boolean untuk semua baris yang memenuhi kondisi Catatan jika ada elemen di baris gagal kondisi baris ditandai salah

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

Akhirnya menyaring baris dari bingkai data berdasarkan kondisi

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

Anda dapat menetapkannya kembali ke df untuk benar-benar menghapus vs filter yang dilakukan di atas
df = df[(df > 0).all(axis=1)]

Ini dapat dengan mudah diperluas untuk menyaring baris yang mengandung NaN (entri non numerik): -
df = df[(~df.isnull()).all(axis=1)]

Ini juga dapat disederhanakan untuk kasus-kasus seperti: Hapus semua baris di mana kolom E negatif

df = df[(df.E>0)]

Saya ingin mengakhiri dengan beberapa statistik profil tentang mengapa @ Pengguna drop solusi lebih lambat daripada penyaringan berbasis kolom mentah: -

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Sebuah kolom pada dasarnya adalah Seriesyaitu NumPyarray, dapat diindeks tanpa biaya apapun. Untuk orang-orang yang tertarik dengan bagaimana organisasi memori yang mendasari bermain dalam kecepatan eksekusi di sini adalah Tautan yang hebat untuk Mempercepat Pandas :

Zakir
sumber
6

Di panda, Anda bisa melakukannya str.lendengan batas Anda dan menggunakan hasil Boolean untuk memfilternya.

df[df['column name'].str.len().lt(2)]
YOBEN_S
sumber
3

Jika Anda ingin menjatuhkan baris bingkai data berdasarkan beberapa kondisi rumit pada nilai kolom kemudian menulis bahwa dengan cara yang ditunjukkan di atas bisa rumit. Saya punya solusi sederhana berikut yang selalu berhasil. Mari kita asumsikan bahwa Anda ingin menjatuhkan kolom dengan 'tajuk' jadi dapatkan kolom itu dalam daftar terlebih dahulu.

text_data = df['name'].tolist()

sekarang terapkan beberapa fungsi pada setiap elemen daftar dan letakkan di dalam seri panda:

text_length = pd.Series([func(t) for t in text_data])

dalam kasus saya, saya hanya mencoba untuk mendapatkan jumlah token:

text_length = pd.Series([len(t.split()) for t in text_data])

sekarang tambahkan satu kolom tambahan dengan seri di atas dalam bingkai data:

df = df.assign(text_length = text_length .values)

sekarang kita dapat menerapkan kondisi pada kolom baru seperti:

df = df[df.text_length  >  10]
def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df
jayanti prasad
sumber