Saya memiliki solusi yang tidak optimal untuk suatu masalah dan saya sedang mencari solusi yang lebih baik.

Data saya terlihat seperti ini:

df = pd.DataFrame(columns=['id', 'score', 'duration', 'user'],
                  data=[[1, 800, 60, 'abc'], [1, 900, 60, 'zxc'], [2, 800, 250, 'abc'], [2, 5000, 250, 'bvc'],
                        [3, 6000, 250, 'zxc'], [3, 8000, 250, 'klp'], [4, 1400, 500,'kod'],
                        [4, 8000, 500, 'bvc']])```

Seperti yang Anda lihat, instance adalah pasangan id identik dengan durasi yang sama dan skor yang berbeda. Tujuan saya adalah untuk menghapus semua pasangan id yang memiliki durasi kurang dari 120 atau di mana setidaknya satu pengguna memiliki skor kurang dari 1500.

Sejauh ini solusi saya seperti ini:

# remove instances with duration > 120 (duration is the same for every instance of the same id)
df= df[df['duration'] > 120]

# groupby id and get the min value of score
test= df.groupby('id')['score'].min().reset_index()

# then I can get a list of the id's where at least one user has a score below 1500 and remove both instances with the same id

for x in list(test[test['score'] < 1500]['id']):
    df.drop(df.loc[df['id']==x].index, inplace=True)

Namun, bit terakhir tidak terlalu efisien dan cukup lambat. Saya memiliki sekitar 700 ribu instance di df dan bertanya-tanya apa cara paling efisien untuk menghapus semua instance dengan id sama dengan yang ditemukan di daftar (test[test['score'] < 1500]['id']). Juga catatan, untuk kesederhanaan saya menggunakan integer untuk id dalam contoh ini tetapi id saya adalah objek yang memiliki format semacam ini 4240c195g794530fj4e10z53.

Namun, Anda dipersilakan untuk menunjukkan kepada saya pendekatan awal yang lebih baik untuk masalah ini. Terima kasih!

2
idontknowmuch 5 April 2021, 17:52

2 jawaban

Jawaban Terbaik

Anda dapat membuat kondisi terlebih dahulu, lalu mengelompokkannya pada kolom boolean berdasarkan kolom id dan kemudian mentransformasikannya dengan all untuk mempertahankan grup yang memenuhi kondisi untuk semua baris dalam grup.

#retain duration greater than or equal to (ge) 120 and id that has score ge 1500
cond = df['duration'].ge(120) & df['score'].ge(1500)
out = df[cond.groupby(df['id']).transform('all')]

Atau merantai mereka dalam 1 baris:

out = df[(df['duration'].ge(120) & df['score'].ge(1500))
                    .groupby(df['id']).transform('all')]

   id  score  duration user
4   3   6000       250  zxc
5   3   8000       250  klp
1
anky 5 April 2021, 15:23

Membuat loop untuk memproses pandas dataframe atau numpy hampir selalu merupakan ide yang buruk berkaitan dengan kinerja. Anda perlu menggunakan metode pandas atau numpy, kecuali metode apply yang tidak begitu efektif.

Saya menambahkan respons anky dan menambahkan dua solusi lain yang sedikit kurang berkinerja.


def with_isin(df):
    df= df[df['duration'] > 120]
    test= df.groupby('id')['score'].min()<1500
    return df.isin(test[test].index)

def with_join(df):
    df= df[df['duration'] > 120]
    test= df.groupby('id')['score'].min()<1500
    return df[df.join(test,rsuffix='_test', on='id')['score_test']]

def anky(df):
    return df[(df['duration'].ge(120) & df['score'].ge(1500))
                    .groupby(df['id']).transform('all')]

%timeit with_isin(df)
#>>> 1.22 ms ± 18.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit with_join(df)
#>>> 2.23 ms ± 48.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


%timeit anky(df)
#>>> 1.15 ms ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

0
Wli 5 April 2021, 18:34