Lors de la sélection d'une seule colonne dans un DataFrame pandas (disons df.iloc[:, 0]
, df['A']
o df.A
etc.), le vecteur résultant est automatiquement converti en une série au lieu d'un DataFrame à une seule colonne. Cependant, j'écris certaines fonctions qui prennent un DataFrame comme argument d'entrée. Par conséquent, je préfère traiter un DataFrame à une seule colonne plutôt qu'une série afin que la fonction puisse supposer que df.columns est accessible. Actuellement, je dois convertir explicitement les séries en DataFrame en utilisant quelque chose comme pd.DataFrame(df.iloc[:, 0])
. Cela ne semble pas être la méthode la plus propre. Existe-t-il une manière plus élégante d'indexer directement à partir d'un DataFrame afin que le résultat soit un DataFrame à une seule colonne au lieu d'une série ?
Réponses
Trop de publicités?Comme le mentionne @Jeff, il y a plusieurs façons de le faire, mais je recommande d'utiliser loc/iloc pour être plus explicite (et de lever les erreurs rapidement si vous essayez quelque chose d'ambigu) :
In [10]: df = pd.DataFrame([[1, 2], [3, 4]], columns=['A', 'B'])
In [11]: df
Out[11]:
A B
0 1 2
1 3 4
In [12]: df[['A']]
In [13]: df[[0]]
In [14]: df.loc[:, ['A']]
In [15]: df.iloc[:, [0]]
Out[12-15]: # they all return the same thing:
A
0 1
1 3
Ces deux derniers choix permettent de lever l'ambiguïté dans le cas de noms de colonnes entières (c'est précisément la raison pour laquelle loc/iloc ont été créés). Par exemple :
In [16]: df = pd.DataFrame([[1, 2], [3, 4]], columns=['A', 0])
In [17]: df
Out[17]:
A 0
0 1 2
1 3 4
In [18]: df[[0]] # ambiguous
Out[18]:
A
0 1
1 3
Comme Andy Hayden recommande l'utilisation de .iloc/.loc pour indexer un cadre de données (à une seule colonne) ; un autre point à noter est la façon d'exprimer les positions d'index. Utilisez un Liste des étiquettes/positions de l'index tout en spécifiant les valeurs de l'argument à indexer en tant que Dataframe ; si ce n'est pas le cas, un message 'pandas.core.series.Series' sera renvoyé.
Entrée :
A_1 = train_data.loc[:,'Fraudster']
print('A_1 is of type', type(A_1))
A_2 = train_data.loc[:, ['Fraudster']]
print('A_2 is of type', type(A_2))
A_3 = train_data.iloc[:,12]
print('A_3 is of type', type(A_3))
A_4 = train_data.iloc[:,[12]]
print('A_4 is of type', type(A_4))
Sortie :
A_1 is of type <class 'pandas.core.series.Series'>
A_2 is of type <class 'pandas.core.frame.DataFrame'>
A_3 is of type <class 'pandas.core.series.Series'>
A_4 is of type <class 'pandas.core.frame.DataFrame'>
Ces trois approches ont été mentionnées :
pd.DataFrame(df.loc[:, 'A']) # Approach of the original post
df.loc[:,[['A']] # Approach 2 (note: use iloc for positional indexing)
df[['A']] # Approach 3
pd.Series.to_frame() est une autre approche.
Comme il s'agit d'une méthode, elle peut être utilisée dans des situations où les deuxième et troisième approches ci-dessus ne s'appliquent pas. En particulier, elle est utile lorsque vous appliquez une méthode à une colonne de votre cadre de données et que vous souhaitez convertir la sortie en un cadre de données plutôt qu'en une série. Par exemple, dans un Jupyter Notebook, une série n'aura pas une belle sortie, mais un dataframe en aura une.
# Basic use case:
df['A'].to_frame()
# Use case 2 (this will give you pretty output in a Jupyter Notebook):
df['A'].describe().to_frame()
# Use case 3:
df['A'].str.strip().to_frame()
# Use case 4:
def some_function(num):
...
df['A'].apply(some_function).to_frame()
(Parler des pandas 1.3.4)
Je voudrais ajouter un peu plus de contexte aux réponses impliquant .to_frame()
. Si vous sélectionnez une seule ligne d'un cadre de données et que vous exécutez la commande .to_frame()
sur cela, alors l'index sera composé des noms de colonnes originaux et vous obtiendrez des noms de colonnes numériques. Vous pouvez simplement ajouter un .T
à la fin pour transposer le tout dans le format du cadre de données original (voir ci-dessous).
import pandas as pd
print(pd.__version__) #1.3.4
df = pd.DataFrame({
"col1": ["a", "b", "c"],
"col2": [1, 2, 3]
})
# series
df.loc[0, ["col1", "col2"]]
# dataframe (column names are along the index; not what I wanted)
df.loc[0, ["col1", "col2"]].to_frame()
# 0
# col1 a
# col2 1
# looks like an actual single-row dataframe.
# To me, this is the true answer to the question
# because the output matches the format of the
# original dataframe.
df.loc[0, ["col1", "col2"]].to_frame().T
# col1 col2
# 0 a 1
# this works really well with .to_dict(orient="records") which is
# what I'm ultimately after by selecting a single row
df.loc[0, ["col1", "col2"]].to_frame().T.to_dict(orient="records")
# [{'col1': 'a', 'col2': 1}]