Créer un Pandas DataFrame vide, puis le remplir?

463

Je commence à partir des documents pandas DataFrame ici: http://pandas.pydata.org/pandas-docs/stable/dsintro.html

Je voudrais remplir le DataFrame de manière itérative avec des valeurs dans un type de calcul de série chronologique. Donc, fondamentalement, je voudrais initialiser le DataFrame avec les colonnes A, B et les lignes d'horodatage, toutes 0 ou toutes NaN.

Je voudrais ensuite ajouter des valeurs initiales et passer en revue ces données en calculant la nouvelle ligne à partir de la ligne précédente, disons row[A][t] = row[A][t-1]+1.

J'utilise actuellement le code ci-dessous, mais je pense que c'est un peu moche et il doit y avoir un moyen de le faire directement avec un DataFrame, ou tout simplement un meilleur moyen en général. Remarque: j'utilise Python 2.7.

import datetime as dt
import pandas as pd
import scipy as s

if __name__ == '__main__':
    base = dt.datetime.today().date()
    dates = [ base - dt.timedelta(days=x) for x in range(0,10) ]
    dates.sort()

    valdict = {}
    symbols = ['A','B', 'C']
    for symb in symbols:
        valdict[symb] = pd.Series( s.zeros( len(dates)), dates )

    for thedate in dates:
        if thedate > dates[0]:
            for symb in valdict:
                valdict[symb][thedate] = 1+valdict[symb][thedate - dt.timedelta(days=1)]

    print valdict
Matthias Kauer
la source
6
Ne développez jamais un DataFrame! Il est toujours moins cher de l'ajouter à une liste python, puis de le convertir en DataFrame à la fin, à la fois en termes de mémoire et de performances.
cs95
@ cs95 Qu'est-ce qui est fonctionnellement différent entre .appenden pd et en ajoutant une liste? Je sais que .appenddans les pandas, tout le jeu de données est copié vers un nouvel objet ´, les pythons s'ajoutent-ils différemment?
Lamma
@Lamma veuillez trouver des détails dans ma réponse ci-dessous. Lors de l'ajout à df, un nouveau DataFrame est créé à chaque fois en mémoire au lieu d'utiliser celui existant, ce qui est franchement un gaspillage.
cs95

Réponses:

330

Voici quelques suggestions:

Utilisation date_range pour l'index:

import datetime
import pandas as pd
import numpy as np

todays_date = datetime.datetime.now().date()
index = pd.date_range(todays_date-datetime.timedelta(10), periods=10, freq='D')

columns = ['A','B', 'C']

Remarque: nous pourrions créer un DataFrame vide (avec NaN s) simplement en écrivant:

df_ = pd.DataFrame(index=index, columns=columns)
df_ = df_.fillna(0) # with 0s rather than NaNs

Pour effectuer ce type de calculs pour les données, utilisez un tableau numpy:

data = np.array([np.arange(10)]*3).T

Par conséquent, nous pouvons créer le DataFrame:

In [10]: df = pd.DataFrame(data, index=index, columns=columns)

In [11]: df
Out[11]: 
            A  B  C
2012-11-29  0  0  0
2012-11-30  1  1  1
2012-12-01  2  2  2
2012-12-02  3  3  3
2012-12-03  4  4  4
2012-12-04  5  5  5
2012-12-05  6  6  6
2012-12-06  7  7  7
2012-12-07  8  8  8
2012-12-08  9  9  9
Andy Hayden
la source
2
pd.date_range () ne fonctionne pas pour moi. J'ai essayé avec DateRange (de l'autocomplétion d'Eclipse), mais cela fonctionne avec des chaînes comme format de date, non? L'approche globale fonctionne cependant (j'ai changé l'index en autre chose).
Matthias Kauer
2
date_range est une fonction d'usine pour créer des index datetime et était une nouvelle fonctionnalité dans 0.8.0 , je recommanderais certainement la mise à niveau vers la dernière version stable (0.9.1), il existe de nombreuses corrections de bugs et de nouvelles fonctionnalités. :)
Andy Hayden
26
D'après mes expériences, créer un bloc de données de la taille nécessaire rempli de NaN, puis remplir avec des valeurs est beaucoup plus lent que de créer un bloc de données avec indexx 0dimensions ( columns = []) et d'attacher une colonne à chaque tour de boucle. Je veux dire df[col_name] = pandas.Series([...])dans une boucle itérative à travers les noms de colonnes. Dans le premier cas, non seulement l'allocation de mémoire prend du temps, mais le remplacement des NaN par de nouvelles valeurs semble extrêmement lent.
deeenes
5
@deeenes définitivement. cette réponse devrait probablement rendre cela plus clair - vous voulez très rarement (voire jamais) créer un Dataframe vide (de NaNs).
Andy Hayden
1
Selon cette réponse stackoverflow.com/a/30267881/2302569 Vous devez attribuer le résultat de fillna ou passer param inplace = True
JayJay
169

Si vous souhaitez simplement créer un bloc de données vide et le remplir ultérieurement avec des blocs de données entrants, essayez ceci:

newDF = pd.DataFrame() #creates a new dataframe that's empty
newDF = newDF.append(oldDF, ignore_index = True) # ignoring index is optional
# try printing some data from newDF
print newDF.head() #again optional 

Dans cet exemple, j'utilise ce document pandas pour créer un nouveau bloc de données, puis j'utilise append bloc de données, puis j'utilise pour écrire dans le newDF avec les données de oldDF.

Si je dois continuer à ajouter de nouvelles données dans ce nouveau FD à partir de plusieurs anciens FD, j'utilise simplement une boucle for pour itérer sur pandas.DataFrame.append ()

geekidharsh
la source
14
Veuillez noter que append(et de la même manière concat) copie le jeu de données complet dans un nouvel objet à chaque fois, par conséquent, l'itération et l'ajout peuvent et entraîneront un impact majeur sur les performances. pour plus d'informations, reportez-vous à: pandas.pydata.org/pandas-docs/stable/merging.html
MoustafaAAtta
4
@MoustafaAAtta Quelles sont les alternatives pour ajouter des données de manière itérative à la trame de données?
MysteryGuy
2
@MoustafaAAtta La réponse de Fred dans ce post: stackoverflow.com/questions/10715965/… est-elle meilleure sur ce point de vue?
MysteryGuy
@MoustafaAAtta vous pouvez peut-être ajouter seulement des lignes à une trame de données, cela créera toujours un nouvel objet mais pour des ensembles de données plus petits, cela pourrait être utile. pandas.pydata.org/pandas-docs/stable/user_guide/…
geekidharsh
136

La bonne façon ™ de créer un DataFrame

TLDR; (il suffit de lire le texte en gras)

La plupart des réponses ici vous diront comment créer un DataFrame vide et le remplir, mais personne ne vous dira que c'est une mauvaise chose à faire.

Voici mon conseil: attendez jusqu'à ce que vous soyez sûr d'avoir toutes les données dont vous avez besoin pour travailler. Utilisez une liste pour collecter vos données, puis initialisez un DataFrame lorsque vous êtes prêt.

data = []
for a, b, c in some_function_that_yields_data():
    data.append([a, b, c])

df = pd.DataFrame(data, columns=['A', 'B', 'C'])

Il est toujours moins coûteux d'ajouter à une liste et de créer un DataFrame en une seule fois que de créer un DataFrame vide (ou l'un des NaN) et de l'ajouter encore et encore. Les listes prennent également moins de mémoire et sont une structure de données beaucoup plus légère avec laquelle travailler , ajouter et supprimer (si nécessaire).

L'autre avantage de cette méthode est dtypesautomatiquement déduit (plutôt que de les affecter objectà tous).

Le dernier avantage est que a RangeIndexest automatiquement créé pour vos données , c'est donc une chose de moins à s'inquiéter (jetez un œil aux pauvres appendet aux locméthodes ci-dessous, vous verrez des éléments dans les deux qui nécessitent une gestion appropriée de l'index).


Choses que vous ne devriez PAS faire

appendou à l' concatintérieur d'une boucle

Voici la plus grosse erreur que j'ai vue des débutants:

df = pd.DataFrame(columns=['A', 'B', 'C'])
for a, b, c in some_function_that_yields_data():
    df = df.append({'A': i, 'B': b, 'C': c}, ignore_index=True) # yuck
    # or similarly,
    # df = pd.concat([df, pd.Series({'A': i, 'B': b, 'C': c})], ignore_index=True)

La mémoire est réattribuée pour chaque appendou concatopération que vous avez. Ajoutez à cela une boucle et vous aurez une opération de complexité quadratique . Depuis la df.appendpage doc :

L'ajout itératif de lignes à un DataFrame peut nécessiter plus de calculs qu'un simple concaténé. Une meilleure solution consiste à ajouter ces lignes à une liste, puis à concaténer la liste avec le DataFrame d'origine à la fois.

L'autre erreur associée df.appendest que les utilisateurs ont tendance à oublier que l' ajout n'est pas une fonction sur place , donc le résultat doit être attribué à nouveau. Vous devez également vous soucier des dtypes:

df = pd.DataFrame(columns=['A', 'B', 'C'])
df = df.append({'A': 1, 'B': 12.3, 'C': 'xyz'}, ignore_index=True)

df.dtypes
A     object   # yuck!
B    float64
C     object
dtype: object

Traiter des colonnes d'objets n'est jamais une bonne chose, car les pandas ne peuvent pas vectoriser les opérations sur ces colonnes. Vous devrez le faire pour le réparer:

df.infer_objects().dtypes
A      int64
B    float64
C     object
dtype: object

loc à l'intérieur d'une boucle

J'ai également vu locutilisé pour ajouter à un DataFrame créé vide:

df = pd.DataFrame(columns=['A', 'B', 'C'])
for a, b, c in some_function_that_yields_data():
    df.loc[len(df)] = [a, b, c]

Comme précédemment, vous n'avez pas pré-alloué la quantité de mémoire dont vous avez besoin à chaque fois, donc la mémoire est agrandie chaque fois que vous créez une nouvelle ligne . C'est aussi mauvais queappend et encore plus laid.

Cadre de données vide de NaNs

Et puis, il y a la création d'un DataFrame de NaNs, et toutes les mises en garde qui y sont associées.

df = pd.DataFrame(columns=['A', 'B', 'C'], index=range(5))
df
     A    B    C
0  NaN  NaN  NaN
1  NaN  NaN  NaN
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  NaN  NaN  NaN

Il crée un DataFrame de colonnes d'objets, comme les autres.

df.dtypes
A    object  # you DON'T want this
B    object
C    object
dtype: object

L'ajout a toujours tous les problèmes comme les méthodes ci-dessus.

for i, (a, b, c) in enumerate(some_function_that_yields_data()):
    df.iloc[i] = [a, b, c]

La preuve est dans le pudding

La synchronisation de ces méthodes est le moyen le plus rapide de voir à quel point elles diffèrent en termes de mémoire et d'utilité.

entrez la description de l'image ici

Code de référence pour référence.

cs95
la source
6
L'ajout d'une liste devrait être le meilleur moyen pour ce type de question
YOBEN_S
9
Cela doit être voté un million de fois plus. Ne développez jamais une trame de données!
Buggy
3
@ user3293236 Dommage que vous deviez recommencer par le bas chaque fois que vous répondez à une ancienne question;)
cs95
2
C'est l'une des choses que je déteste le plus. Ces nombreuses fois, vous voyez le 𝒓𝒆𝒂𝒍 𝒄𝒐𝒓𝒓𝒆𝒄𝒕 𝒂𝒏𝒔𝒘𝒆𝒓 qui reste quelque part avec peu de votes et jamais accepté. Je manque du code avec 𝚍𝚏 = 𝚙𝚍.𝙳𝚊𝚝𝚊𝙵𝚛𝚊𝚖𝚎 ([]) pour créer une trame de données pandas vide. Vote en faveur de cette réponse. Excellente explication, @ cs95!
jonathan
1
C'est littéralement dans la documentation. "L'ajout itératif de lignes à un DataFrame peut nécessiter plus de calculs qu'un seul concaténat. Une meilleure solution consiste à ajouter ces lignes à une liste, puis à concaténer la liste avec le DataFrame d'origine en une seule fois." pandas.pydata.org/pandas-docs/version/0.21/generated/…
endolith
132

Initialiser un cadre vide avec des noms de colonne

import pandas as pd

col_names =  ['A', 'B', 'C']
my_df  = pd.DataFrame(columns = col_names)
my_df

Ajouter un nouvel enregistrement à un cadre

my_df.loc[len(my_df)] = [2, 4, 5]

Vous pouvez également vouloir passer un dictionnaire:

my_dic = {'A':2, 'B':4, 'C':5}
my_df.loc[len(my_df)] = my_dic 

Ajoutez un autre cadre à votre cadre existant

col_names =  ['A', 'B', 'C']
my_df2  = pd.DataFrame(columns = col_names)
my_df = my_df.append(my_df2)

Considérations sur les performances

Si vous ajoutez des lignes dans une boucle, tenez compte des problèmes de performances. Pour les 1000 premiers enregistrements environ, les performances de "my_df.loc" sont meilleures, mais elles ralentissent progressivement en augmentant le nombre d'enregistrements dans la boucle.

Si vous prévoyez de faire des minces dans une grande boucle (disons des enregistrements de 10M‌ environ), il vaut mieux utiliser un mélange de ces deux; remplissez un cadre de données avec iloc jusqu'à ce que la taille atteigne environ 1000, puis ajoutez-le au cadre de données d'origine et videz le cadre de données temporaire. Cela augmenterait vos performances d'environ 10 fois.

Afshin Amiri
la source
my_df = my_df.append(my_df2)ne fonctionne pas pour moi sauf si je le précise ignore_index=True.
Nasif Imtiaz Ohi
0

Supposons une trame de données avec 19 lignes

index=range(0,19)
index

columns=['A']
test = pd.DataFrame(index=index, columns=columns)

Conserver la colonne A comme constante

test['A']=10

Garder la colonne b comme variable donnée par une boucle

for x in range(0,19):
    test.loc[[x], 'b'] = pd.Series([x], index = [x])

Vous pouvez remplacer le premier x dans pd.Series([x], index = [x])n'importe quelle valeur

Ajay Ohri
la source