Comment diviser une colonne en deux colonnes?

196

J'ai un bloc de données avec une colonne et je voudrais le diviser en deux colonnes, avec un en-tête de colonne comme ' fips'et l'autre'row'

Mon dataframe dfressemble à ceci:

          row
0    00000 UNITED STATES
1    01000 ALABAMA
2    01001 Autauga County, AL
3    01003 Baldwin County, AL
4    01005 Barbour County, AL

Je ne sais pas comment utiliser df.row.str[:]pour atteindre mon objectif de division de la cellule de ligne. Je peux utiliser df['fips'] = hellopour ajouter une nouvelle colonne et la remplir avec hello. Des idées?

         fips       row
0    00000 UNITED STATES
1    01000 ALABAMA 
2    01001 Autauga County, AL
3    01003 Baldwin County, AL
4    01005 Barbour County, AL
ak
la source
3
comment avez-vous chargé vos données dans des pandas? Vous pourrez peut-être charger les données dans le format souhaité en utilisant read_table()or read_fwf()
zach

Réponses:

137

Il pourrait y avoir une meilleure façon, mais voici une approche:

                            row
    0       00000 UNITED STATES
    1             01000 ALABAMA
    2  01001 Autauga County, AL
    3  01003 Baldwin County, AL
    4  01005 Barbour County, AL
df = pd.DataFrame(df.row.str.split(' ',1).tolist(),
                                 columns = ['flips','row'])
   flips                 row
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL
racine
la source
6
Sachez que .tolist () supprimera tous les index que vous aviez, donc votre nouveau Dataframe sera réindexé à partir de 0 (cela n'a pas d'importance dans votre cas spécifique).
Crashthatch
10
@Crashthatch - là encore, vous pouvez simplement ajouter index = df.indexet vous êtes bon.
root
que faire si une cellule ne peut pas être divisée?
Nisba
@Nisba: Si aucune cellule ne peut être divisée (par exemple, la chaîne ne contient pas d'espace pour ce cas), cela fonctionnera toujours, mais une partie du fractionnement sera vide. D'autres situations se produiront si vous avez des types mixtes dans la colonne avec au moins une cellule contenant n'importe quel type de nombre. Ensuite, la splitméthode retourne NaN et la tolistméthode renverra cette valeur telle quelle (NaN), ce qui entraînera ValueError(pour résoudre ce problème, vous pouvez le convertir en type chaîne avant de fractionner). Je vous recommande de l'essayer par vous-même, c'est la meilleure façon d'apprendre :-)
Nerxis
@techkuz: Êtes-vous sûr que votre dfa l'en row-tête de colonne? Vous pouvez penser que c'est une sorte d'attribut DataFrame mais il est assez clair que c'est le nom de la colonne. C'est à vous de voir comment vous créez et définissez vos en-têtes de colonne, donc si vous en utilisez un autre, utilisez-le (par exemple df.my_column_name.split(...)).
Nerxis
389

Version TL; DR:

Pour le cas simple de:

  • J'ai une colonne de texte avec un délimiteur et je veux deux colonnes

La solution la plus simple est:

df['A'], df['B'] = df['AB'].str.split(' ', 1).str

Ou vous pouvez créer créer un DataFrame avec une colonne pour chaque entrée de la division automatiquement avec:

df['AB'].str.split(' ', 1, expand=True)

Vous devez utiliser expand=Truesi vos chaînes ont un nombre non uniforme de fractionnements et que vous souhaitez Noneremplacer les valeurs manquantes.

Remarquez comment, dans les deux cas, la .tolist()méthode n'est pas nécessaire. Ni l'un ni l'autre zip().

En détail:

La solution d'Andy Hayden est la plus excellente pour démontrer la puissance de la str.extract()méthode.

Mais pour un simple fractionnement sur un séparateur connu (comme le fractionnement par des tirets ou le fractionnement par des espaces), la .str.split()méthode est suffisante 1 . Il fonctionne sur une colonne (série) de chaînes et renvoie une colonne (série) de listes:

>>> import pandas as pd
>>> df = pd.DataFrame({'AB': ['A1-B1', 'A2-B2']})
>>> df

      AB
0  A1-B1
1  A2-B2
>>> df['AB_split'] = df['AB'].str.split('-')
>>> df

      AB  AB_split
0  A1-B1  [A1, B1]
1  A2-B2  [A2, B2]

1: Si vous n'êtes pas sûr de ce que font les deux premiers paramètres .str.split(), je recommande les documents pour la version Python standard de la méthode .

Mais comment allez-vous:

  • une colonne contenant des listes à deux éléments

à:

  • deux colonnes, chacune contenant l'élément respectif des listes?

Eh bien, nous devons examiner de plus près l' .strattribut d'une colonne.

C'est un objet magique qui est utilisé pour collecter des méthodes qui traitent chaque élément d'une colonne comme une chaîne, puis appliquent la méthode respective dans chaque élément aussi efficacement que possible:

>>> upper_lower_df = pd.DataFrame({"U": ["A", "B", "C"]})
>>> upper_lower_df

   U
0  A
1  B
2  C
>>> upper_lower_df["L"] = upper_lower_df["U"].str.lower()
>>> upper_lower_df

   U  L
0  A  a
1  B  b
2  C  c

Mais il a aussi une interface "d'indexation" pour récupérer chaque élément d'une chaîne par son index:

>>> df['AB'].str[0]

0    A
1    A
Name: AB, dtype: object

>>> df['AB'].str[1]

0    1
1    2
Name: AB, dtype: object

Bien sûr, cette interface d'indexation de .strne se soucie pas vraiment si chaque élément qu'il indexe est en fait une chaîne, tant qu'il peut être indexé, donc:

>>> df['AB'].str.split('-', 1).str[0]

0    A1
1    A2
Name: AB, dtype: object

>>> df['AB'].str.split('-', 1).str[1]

0    B1
1    B2
Name: AB, dtype: object

Ensuite, c'est une simple question de tirer parti du déballage du tuple Python des itérables pour faire

>>> df['A'], df['B'] = df['AB'].str.split('-', 1).str
>>> df

      AB  AB_split   A   B
0  A1-B1  [A1, B1]  A1  B1
1  A2-B2  [A2, B2]  A2  B2

Bien sûr, extraire un DataFrame du fractionnement d'une colonne de chaînes est si utile que la .str.split()méthode peut le faire pour vous avec le expand=Trueparamètre:

>>> df['AB'].str.split('-', 1, expand=True)

    0   1
0  A1  B1
1  A2  B2

Donc, une autre façon d'accomplir ce que nous voulions est de faire:

>>> df = df[['AB']]
>>> df

      AB
0  A1-B1
1  A2-B2

>>> df.join(df['AB'].str.split('-', 1, expand=True).rename(columns={0:'A', 1:'B'}))

      AB   A   B
0  A1-B1  A1  B1
1  A2-B2  A2  B2

La expand=Trueversion, bien que plus longue, présente un net avantage sur la méthode de décompression de tuple. Le déballage de tuple ne gère pas bien les divisions de différentes longueurs:

>>> df = pd.DataFrame({'AB': ['A1-B1', 'A2-B2', 'A3-B3-C3']})
>>> df
         AB
0     A1-B1
1     A2-B2
2  A3-B3-C3
>>> df['A'], df['B'], df['C'] = df['AB'].str.split('-')
Traceback (most recent call last):
  [...]    
ValueError: Length of values does not match length of index
>>> 

Mais le expand=Truegère bien en plaçant Nonedans les colonnes pour lesquelles il n'y a pas assez de "divisions":

>>> df.join(
...     df['AB'].str.split('-', expand=True).rename(
...         columns={0:'A', 1:'B', 2:'C'}
...     )
... )
         AB   A   B     C
0     A1-B1  A1  B1  None
1     A2-B2  A2  B2  None
2  A3-B3-C3  A3  B3    C3
LeoRochael
la source
df ['A'], df ['B'] = df ['AB']. str.split ('', 1) .str Quelle est la signification de '1' en split ('', 1)?
Hariprasad
@Hariprasad, c'est le nombre maximum de divisions. J'ai ajouté un lien vers les documents pour la version Python de la .split()méthode qui explique mieux les deux premiers paramètres que les documents Pandas.
LeoRochael
5
pandas 1.0.0 rapporte "FutureWarning: l'itération en colonnes sur les personnages sera déconseillée dans les prochaines versions."
Frank
1
Cela fonctionne sous Python 1.0.1. df.join(df['AB'].str.split('-', 1, expand=True).rename(columns={0:'A', 1:'B'}))
Martien Lubberink
59

Vous pouvez extraire les différentes parties de manière assez nette en utilisant un modèle d'expression régulière:

In [11]: df.row.str.extract('(?P<fips>\d{5})((?P<state>[A-Z ]*$)|(?P<county>.*?), (?P<state_code>[A-Z]{2}$))')
Out[11]: 
    fips                    1           state           county state_code
0  00000        UNITED STATES   UNITED STATES              NaN        NaN
1  01000              ALABAMA         ALABAMA              NaN        NaN
2  01001   Autauga County, AL             NaN   Autauga County         AL
3  01003   Baldwin County, AL             NaN   Baldwin County         AL
4  01005   Barbour County, AL             NaN   Barbour County         AL

[5 rows x 5 columns]

Pour expliquer le regex quelque peu long:

(?P<fips>\d{5})
  • Correspond aux cinq chiffres ( \d) et les nomme "fips".

La partie suivante:

((?P<state>[A-Z ]*$)|(?P<county>.*?), (?P<state_code>[A-Z]{2}$))

Soit ( |) l'une des deux choses suivantes:

(?P<state>[A-Z ]*$)
  • Correspond à n'importe quel nombre ( *) de majuscules ou d'espaces ( [A-Z ]) et le nomme "state"avant la fin de la chaîne ( $),

ou

(?P<county>.*?), (?P<state_code>[A-Z]{2}$))
  • correspond à autre chose ( .*) puis
  • une virgule et un espace puis
  • correspond aux deux chiffres state_codeavant la fin de la chaîne ( $).

Dans l'exemple:
Notez que les deux premières lignes atteignent "l'état" (en laissant NaN dans les colonnes comté et état_code), tandis que les trois dernières frappent le comté, état_code (en laissant NaN dans la colonne état).

Andy Hayden
la source
C'est certainement la meilleure solution, mais cela pourrait être un peu écrasant pour certains avec le regex très étendu. Pourquoi ne pas le faire en tant que partie 2 et avoir la partie 1 avec juste les colonnes fips et row?
Little Bobby Tables
2
@josh c'est un bon point, alors que les différentes parties de l'expression régulière sont "faciles" à comprendre, une expression régulière longue peut se compliquer rapidement. J'ai ajouté quelques explications pour les futurs lecteurs! (J'ai également dû mettre à jour le lien vers les documents qui explique la (?P<label>...)syntaxe! Je ne sais pas pourquoi je suis allé pour le regex plus complexe, clairement le simple pourrait fonctionner hmmmm
Andy Hayden
1
Semble beaucoup plus convivial. Je suis content que vous l'ayez fait car cela m'a fait regarder les documents pour comprendre le <group_name>. Maintenant je le sais, cela rend mon code très succinct.
Little Bobby Tables
44
df[['fips', 'row']] = df['row'].str.split(' ', n=1, expand=True)
Bhagabat Behera
la source
22

Si vous ne souhaitez pas créer un nouveau cadre de données, ou si votre cadre de données comporte plus de colonnes que celles que vous souhaitez fractionner, vous pouvez:

df["flips"], df["row_name"] = zip(*df["row"].str.split().tolist())
del df["row"]  
keberwein
la source
1
Je reçois une zip argument #1 must support iterationerreur, python 2.7
Allan Ruin
20

Vous pouvez utiliser str.splitpar espace (séparateur par défaut) et paramètre expand=Truepour DataFrameavec assigner à de nouvelles colonnes:

df = pd.DataFrame({'row': ['00000 UNITED STATES', '01000 ALABAMA', 
                           '01001 Autauga County, AL', '01003 Baldwin County, AL', 
                           '01005 Barbour County, AL']})
print (df)
                        row
0       00000 UNITED STATES
1             01000 ALABAMA
2  01001 Autauga County, AL
3  01003 Baldwin County, AL
4  01005 Barbour County, AL



df[['a','b']] = df['row'].str.split(n=1, expand=True)
print (df)
                        row      a                   b
0       00000 UNITED STATES  00000       UNITED STATES
1             01000 ALABAMA  01000             ALABAMA
2  01001 Autauga County, AL  01001  Autauga County, AL
3  01003 Baldwin County, AL  01003  Baldwin County, AL
4  01005 Barbour County, AL  01005  Barbour County, AL

Modification si besoin supprimer la colonne d'origine avec DataFrame.pop

df[['a','b']] = df.pop('row').str.split(n=1, expand=True)
print (df)
       a                   b
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Qu'est-ce que c'est comme:

df[['a','b']] = df['row'].str.split(n=1, expand=True)
df = df.drop('row', axis=1)
print (df)

       a                   b
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Si obtenir une erreur:

#remove n=1 for split by all whitespaces
df[['a','b']] = df['row'].str.split(expand=True)

ValueError: les colonnes doivent avoir la même longueur que la clé

Vous pouvez vérifier et retourner 4 colonnes DataFrame, pas seulement 2:

print (df['row'].str.split(expand=True))
       0        1        2     3
0  00000   UNITED   STATES  None
1  01000  ALABAMA     None  None
2  01001  Autauga  County,    AL
3  01003  Baldwin  County,    AL
4  01005  Barbour  County,    AL

Ensuite, la solution est ajoutée DataFrameparjoin :

df = pd.DataFrame({'row': ['00000 UNITED STATES', '01000 ALABAMA', 
                           '01001 Autauga County, AL', '01003 Baldwin County, AL', 
                           '01005 Barbour County, AL'],
                    'a':range(5)})
print (df)
   a                       row
0  0       00000 UNITED STATES
1  1             01000 ALABAMA
2  2  01001 Autauga County, AL
3  3  01003 Baldwin County, AL
4  4  01005 Barbour County, AL

df = df.join(df['row'].str.split(expand=True))
print (df)

   a                       row      0        1        2     3
0  0       00000 UNITED STATES  00000   UNITED   STATES  None
1  1             01000 ALABAMA  01000  ALABAMA     None  None
2  2  01001 Autauga County, AL  01001  Autauga  County,    AL
3  3  01003 Baldwin County, AL  01003  Baldwin  County,    AL
4  4  01005 Barbour County, AL  01005  Barbour  County,    AL

Avec supprimer la colonne d'origine (s'il existe également une autre colonne):

df = df.join(df.pop('row').str.split(expand=True))
print (df)
   a      0        1        2     3
0  0  00000   UNITED   STATES  None
1  1  01000  ALABAMA     None  None
2  2  01001  Autauga  County,    AL
3  3  01003  Baldwin  County,    AL
4  4  01005  Barbour  County,    AL   
jezrael
la source
8

Si vous souhaitez diviser une chaîne en plus de deux colonnes sur la base d'un délimiteur, vous pouvez omettre le paramètre «divisions maximales».
Vous pouvez utiliser:

df['column_name'].str.split('/', expand=True)

Cela créera automatiquement autant de colonnes que le nombre maximal de champs inclus dans l'une de vos chaînes initiales.

Jasmin
la source
6

Surpris, je n'ai pas encore vu celui-ci. Si vous n'avez besoin que de deux scissions, je le recommande vivement. . .

Series.str.partition

partition effectue une séparation sur le séparateur et est généralement assez performant.

df['row'].str.partition(' ')[[0, 2]]

       0                   2
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Si vous devez renommer les lignes,

df['row'].str.partition(' ')[[0, 2]].rename({0: 'fips', 2: 'row'}, axis=1)

    fips                 row
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Si vous devez le joindre à nouveau à l'original, utilisez joinou concat:

df.join(df['row'].str.partition(' ')[[0, 2]])

pd.concat([df, df['row'].str.partition(' ')[[0, 2]]], axis=1)

                        row      0                   2
0       00000 UNITED STATES  00000       UNITED STATES
1             01000 ALABAMA  01000             ALABAMA
2  01001 Autauga County, AL  01001  Autauga County, AL
3  01003 Baldwin County, AL  01003  Baldwin County, AL
4  01005 Barbour County, AL  01005  Barbour County, AL
cs95
la source
0

Je préfère exporter les séries de pandas correspondant ( à savoir les colonnes dont j'ai besoin), en utilisant l' appliquer fonction pour diviser le contenu de la colonne en plusieurs séries, puis rejoindre les colonnes générées à la trame de données existante. Bien sûr, la colonne source doit être supprimée.

par exemple

 col1 = df["<col_name>"].apply(<function>)
 col2 = ...
 df = df.join(col1.to_frame(name="<name1>"))
 df = df.join(col2.toframe(name="<name2>"))
 df = df.drop(["<col_name>"], axis=1)

Pour diviser deux mots, la fonction des chaînes devrait ressembler à ceci:

lambda x: x.split(" ")[0] # for the first element
lambda x: x.split(" ")[-1] # for the last element
mcchran
la source
0

J'ai vu que personne n'avait utilisé la méthode de la tranche, alors ici j'ai mis mes 2 cents ici.

df["<col_name>"].str.slice(stop=5)
df["<col_name>"].str.slice(start=6)

Cette méthode créera deux nouvelles colonnes.

Vingt Cent
la source