1
votes

Faire correspondre les valeurs et les en-têtes des colonnes pandas dans les dataframes

J'ai 3 fichiers que je lis dans des dataframes ( https://pastebin.com/v7BnSH3s )

map_df: mappe les en-têtes data_file aux en-têtes codes_df

# code column to list
gender_codes = codes_df["gender_codes"].tolist()

# remove nan string
gender_codes = [gender_codes 
               for gender_codes in gender_codes
               if  str(gender_codes) != "nan"]

# check each value against code list
result_df = data_df.loc[(~data_df.Gender.isin(gender_codes))]
result_df = result_df.filter(items = ["Name","Gender"])
result_df.rename(columns = {"Gender":"Value"}, inplace = True)
result_df['Column'] = 'Gender'

code_df: Codes valides

Name Value  Column
Alex    99  Gender  

data_df: les données réelles qui doivent être comparées aux codes

Name  Gender  Race   Ethnicity
Alex    99     1         7
Cindy   2      4         5
Tom     1      99        1

Problème: Je dois confirmer que chaque valeur de chaque colonne de data_df est un code valide. Sinon, je dois écrire le Nom , la valeur invalide et le libellé de l'en-tête de colonne comme une nouvelle colonne. Donc, mon exemple data_df donnerait le dataframe suivant pour le contrôle gender_codes :

result_df:

gender_codes   race_codes     ethnicity_codes
1                  1               1
2                  2               2
3                  3               3
4                  4               4
NaN               NaN              5
NaN               NaN              6
NaN               NaN              7

Contexte:

  • Mon fichier de données actuel comporte plus de 100 colonnes.
  • Une colonne de code peut être mappée à plusieurs colonnes dans le data_df .
  • Je n'utilise actuellement pas le map_df autrement que pour savoir à quelles colonnes correspond les colonnes quels codes. Cependant, si je peux incorporer cela dans mon script, ce serait idéal.

Ce que j'ai essayé: J'envoie actuellement chaque colonne de code à une liste, en supprimant la chaîne nan , en effectuant la recherche avec loc et isin , puis en configurant le result_df ...

Field Name             Code Name
Gender              gender_codes
Race                race_codes
Ethnicity           ethnicity_codes

Cela fonctionne mais est évidemment extrêmement primitif et ne s'adapte pas à mon ensemble de données. J'espère trouver une approche itérative et pythonique de ce problème.

MODIFIER: Ensemble de données modifié avec np.nan

https://pastebin.com/v7BnSH3s p>


2 commentaires

donc les colonnes dans codes_df devraient être par gender_code etc. et non par gender_list etc.?


@ anky_91, ouais, je viens de le réparer. Merci


3 Réponses :


1
votes

Vous devrez prétraiter vos dataframes et définir une fonction de validation. Quelque chose comme ci-dessous:

1. Prétraitement

mdata['isValid'] = mdata.apply(isValid, axis=1)
result = mdata[mdata.isValid == False]

Out:

result:
   Name  Column Value  isValid
0  Alex  Gender    99    False
5   Tom    Race    99    False

2. Fonction de validation

def isValid(row):
    valid_list = validation_df[validation_df['Field Name'] == row.Column]['Valid Code'].tolist()
    return row.Value in valid_list

3. Validation

# call melt() to convert columns to rows
mcodes = codes_df.melt(
    value_vars=list(codes_df.columns), 
    var_name='Code Name', 
    value_name='Valid Code').dropna()

mdata = data_df.melt(
    id_vars='Name',
    value_vars=list(data_df.columns[1:]), 
    var_name='Column', 
    value_name='Value')

validation_df = mcodes.merge(map_df, on='Code Name')

Out:

mcodes:
          Code Name Valid Code
0      gender_codes          1
1      gender_codes          2
7        race_codes          1
8        race_codes          2
9        race_codes          3
10       race_codes          4
14  ethnicity_codes          1
15  ethnicity_codes          2
16  ethnicity_codes          3
17  ethnicity_codes          4
18  ethnicity_codes          5
19  ethnicity_codes          6
20  ethnicity_codes          7

mdata:
    Name     Column Value
0   Alex     Gender    99
1  Cindy     Gender     2
2    Tom     Gender     1
3   Alex       Race     1
4  Cindy       Race     4
5    Tom       Race    99
6   Alex  Ethnicity     7
7  Cindy  Ethnicity     5
8    Tom  Ethnicity     1

validation_df:

          Code Name Valid Code Field Name
0      gender_codes          1     Gender
1      gender_codes          2     Gender
2        race_codes          1       Race
3        race_codes          2       Race
4        race_codes          3       Race
5        race_codes          4       Race
6   ethnicity_codes          1  Ethnicity
7   ethnicity_codes          2  Ethnicity
8   ethnicity_codes          3  Ethnicity
9   ethnicity_codes          4  Ethnicity
10  ethnicity_codes          5  Ethnicity
11  ethnicity_codes          6  Ethnicity
12  ethnicity_codes          7  Ethnicity


1 commentaires

C'est aussi une bonne implémentation. J'ai laissé tomber le .dropna () de mcodes afin de transmettre les valeurs NaN. Merci!



2
votes

Indexation booléenne

Je reformaterais vos données sous différentes formes

ddf  # Melted dataframe to help match the final output

    Name     Column  Value
0   Alex     Gender     99
1  Cindy     Gender      2
2    Tom     Gender      1
3   Alex       Race      1
4  Cindy       Race      4
5    Tom       Race     99
6   Alex  Ethnicity      7
7  Cindy  Ethnicity      5
8    Tom  Ethnicity      1

Détails

c  # Series of membership sets

ethnicity_codes    {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}
gender_codes                      {1.0, 2.0, 3.0, 4.0}
race_codes                        {1.0, 2.0, 3.0, 4.0}
dtype: object

m  # Just a dictionary with the same content as `map_df`

{'Gender': 'gender_codes',
 'Race': 'race_codes',
 'Ethnicity': 'ethnicity_codes'}

m = dict(map_df.itertuples(index=False))
c = code_df.T.stack().groupby(level=0).apply(set)
ddf = data_df.melt('Name', var_name='Column', value_name='Value')

ddf[[val not in c[col] for val, col in zip(ddf.Value, ddf.Column.map(m))]]

   Name  Column  Value
0  Alex  Gender     99
5   Tom    Race     99

5 commentaires

Merci! Fonctionne très bien. Après coup, il est possible que data_df ait des valeurs nulles. Si tel est le cas, je pourrais ajouter des critères à la compréhension de la liste " notna () ", n'est-ce pas?


vous pourriez ... mais si vous donniez un exemple, je pourrais probablement trouver quelque chose de plus propre.


gotcha, vient d'ajouter quelques valeurs NaN à data_df ( pastebin.com/v7BnSH3s ). Merci!


L'astuce consiste à obtenir le dictionnaire ou une série d'ensembles acceptables. Je peux ajouter np.nan à ethnicity_codes avec c = code_df.T.stack (). Groupby (level = 0) .apply (set); c.at ['ethnicity_codes'] | = {np.nan}


Ah gotcha, ça a du sens. Je pensais à une solution de contournement beaucoup plus compliquée. Merci encore!



1
votes
m, df1 =  dict(map_df.values), data_df.set_index('Name')

df1[df1.apply(lambda x:~x.isin(code_df[m[x.name]]))].stack().reset_index()

Out:
   Name level_1     0
0  Alex  Gender  99.0
1   Tom    Race  99.0

0 commentaires