2 votes

Comment supprimer les doublons dont l'un a une valeur nulle en Python ?

Problème

Désolé pour tous ceux qui m'ont aidé, mais j'ai dû reformuler ma question. J'ai un dataframe avec des doublons pour la plupart des colonnes, à l'exception de la dernière colonne. Là où j'ai des doublons, je veux appliquer la règle suivante :

  1. Si les deux ont des entrées valides dans la dernière colonne, conserver les deux.
  2. Si les deux ont des entrées nulles dans la dernière colonne, conserver l'une d'entre elles.
  3. Si l'un a une entrée valide et l'autre une entrée nulle, conserver l'entrée valide.

Je souhaite ensuite retirer les valeurs en double et créer un cadre de données distinct. Pour l'instant, mon approche est laborieuse et supprime les deux doublons lorsqu'ils sont tous deux nuls.

Reprex

Démarrage de la base de données

import pandas as pd
import numpy as np

data_input = {'Student':     ['A', 'A',          'B', 'B',            'C',      'D',      'E',      'F', 'F',         'G',     "H",     "H", "I", "I"], 
              "Subject": ["Law", "Law",      "Maths", "Maths",    "Maths", "Law",    "Maths",  "Music", "Music", "Music",      "Art", "Art", "Dance", "Dance"], 
              "Checked":  ["Bob", "James",    np.nan,  "Jack",     "Laura", "Laura",  np.nan,    np.nan, "Tim",   "Tim",       "Tim", np.nan, np.nan, np.nan]}

# Create DataFrame
df1 = pd.DataFrame(data_input)

enter image description here

Résultats souhaités

enter image description here

Première tentative

attempt1 = df1.sort_values(['Student', 'Checked'], ascending=False).drop_duplicates(["Student", "Subject"]).sort_index()

J'ai pris cela dans une autre Q&A sur Stack, mais cela ne me donne pas le résultat que je veux et je ne le comprends pas.

Tentative 2

#Create Duplicate column
df1["Duplicates"] = df1.duplicated(subset=["Student", "Subject"], keep=False)

#Create list of rows with no duplicates
df_new1 = df1[df1["Duplicates"]==False]

#Create list of rows with duplicates & remove all those with null values
#HERE IS WHERE I GET STUCK. IF BOTH DUPLICATES ARE NULLS, I WANT TO KEEP ONE OF THEM
df_new2 = df1[df1["Duplicates"]==True]
df_new3 = df_new2[~df_new2["Checked"].isnull()]

#Combine unique rows, and duplicates without null values
#Keep duplicates without null values
df_new = df_new1.append(df_new3)

#Tidy up
df_new = df_new[["Student", "Subject", "Checked"]].sort_values(by="Student")

df_new

Je peux alors créer une liste des doublons qui apparaissent tous deux valides

#Create separate list of duplicates with valid "Checked" values
df_new["Duplicates"] = df_new.duplicated(subset="Student", keep=False)
conflicting_duplicates = df_new[df_new["Duplicates"]==True]
conflicting_duplicates

Aide

Merci à tous ! Vos réponses m'ont aidé, mais je n'avais pas réalisé que je voulais également conserver l'une des entrées où les deux sont nulles.

Existe-t-il une meilleure façon de procéder ?

2voto

mozway Points 233

Utiliser l'indexation booléenne :

# is the group containing more than one row?
m1 = df1.duplicated(['Student', 'Subject'], keep=False)
# is the row a NaN in "Checked"?
m2 = df1['Checked'].isna()
# both conditions True
m = m1&m2

# keep if either condition is False 
df1[~m]

# to get dropped duplicates
# keep if both are True
df1[m]

Sortie :

   Student Subject Checked
0        A     Law     Bob
1        A     Law   James
3        B   Maths    Jack
4        C   Maths   Laura
5        D     Law   Laura
6        E   Maths     NaN
8        F   Music     Tim
9        G   Music     Tim
10       H     Art     Tim

1voto

sophocles Points 5617

Vous pouvez créer une nouvelle colonne d'aide en utilisant np.where qui marquera les lignes qui satisfont aux conditions que vous avez spécifiées. Si je vous comprends bien, vous souhaitez conserver les lignes qui sont dupliquées dans "Étudiant" et "Sujet", et vous avez également une fonction null dans la colonne "Vérifié".

Vous pouvez alors utiliser loc pour supprimer ceux qui sont marqués :

import numpy as np

df1['to_drop'] = np.where(
    (df1['Student'].isin(df1[df1[['Student','Subject']].duplicated()]['Student'].tolist())
     ) & (df1['Checked'].isnull()),1,0)

df1.loc[df1.to_drop==0].drop('to_drop',axis=1)

empreintes :

   Student Subject Checked
0        A     Law     Bob
1        A     Law   James
3        B   Maths    Jack
4        C   Maths   Laura
5        D     Law   Laura
6        E   Maths     NaN
8        F   Music     Tim
9        G   Music     Tim
10       H     Art     Tim

1voto

Himanshu Poddar Points 120

Vous pouvez utiliser groupby et déposer NaN valeurs

df.groupby("Student", sort=False).apply(lambda x : x if len(x) == 1 else x.dropna(subset=['Checked'])).reset_index(drop=True)

Sortie :

Nous obtenons ainsi le résultat escompté

  Student Subject Checked
0       A     Law     Bob
1       A     Law   James
2       B   Maths    Jack
3       C   Maths   Laura
4       D     Law   Laura
5       E   Maths     NaN
6       F   Music     Tim
7       G   Music     Tim
8       H     Art     Tim

1voto

Yuca Points 4350

Mon point de vue sur l'indexation booléenne :

s_idx = ~(df1[(df1.duplicated(subset=['Student','Subject'],keep=False))])['Checked'].isna()

idx = [x for x in df1.index.values if x not in s_idx[~s_idx].index]

df1.iloc[idx]

Sortie :

   Student Subject Checked
0        A     Law     Bob
1        A     Law   James
3        B   Maths    Jack
4        C   Maths   Laura
5        D     Law   Laura
6        E   Maths     NaN
8        F   Music     Tim
9        G   Music     Tim
10       H     Art     Tim

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X