Les pandas convertissent la trame de données en tableau de tuples

132

J'ai manipulé certaines données à l'aide de pandas et je souhaite maintenant effectuer une sauvegarde par lots dans la base de données. Cela m'oblige à convertir le dataframe en un tableau de tuples, chaque tuple correspondant à une "ligne" du dataframe.

Mon DataFrame ressemble à quelque chose comme:

In [182]: data_set
Out[182]: 
  index data_date   data_1  data_2
0  14303 2012-02-17  24.75   25.03 
1  12009 2012-02-16  25.00   25.07 
2  11830 2012-02-15  24.99   25.15 
3  6274  2012-02-14  24.68   25.05 
4  2302  2012-02-13  24.62   24.77 
5  14085 2012-02-10  24.38   24.61 

Je veux le convertir en un tableau de tuples comme:

[(datetime.date(2012,2,17),24.75,25.03),
(datetime.date(2012,2,16),25.00,25.07),
...etc. ]

Une suggestion sur la façon dont je peux le faire efficacement?

enrishi
la source
21
Pour ceux qui arrivent à cette réponse en 2017+, il existe une nouvelle solution idiomatique ci-dessous . Vous pouvez simplement utiliserlist(df.itertuples(index=False, name=None))
Ted Petrou
3
Les deux choses que je recherche quand j'arrive à cette question: Une liste de tuples - df.to_records(index=False)et une liste de dictionnaires:df.to_dict('records')
Martin Thoma
@MartinThoma à la fois to_records et to_dict ('records') vissent mes types de données. Bug connu mais rend cette solution sans valeur ...
Jochen

Réponses:

206

Que diriez-vous:

subset = data_set[['data_date', 'data_1', 'data_2']]
tuples = [tuple(x) for x in subset.to_numpy()]

pour les pandas <0,24 utilisation

tuples = [tuple(x) for x in subset.values]
Wes McKinney
la source
2
Veuillez consulter la réponse de @ ksindi ci-dessous pour l'utilisation .itertuples, ce qui sera plus efficace que d'obtenir les valeurs sous forme de tableau et de les transformer en un tuple.
vy32
1
légèrement plus propre est: tuples = map (tuple, subset.values)
RufusVS
Cela peut cependant convertir les valeurs en un type différent, non?
AMC
162
list(data_set.itertuples(index=False))

À partir de 17.1, ce qui précède renverra une liste de tuples nommés .

Si vous voulez une liste de tuples ordinaires, passez name=Noneen argument:

list(data_set.itertuples(index=False, name=None))
Kamil Sindi
la source
39
Cela devrait être la réponse acceptée à mon humble avis (maintenant qu'une fonctionnalité dédiée existe). BTW, si vous voulez des tuples normaux dans votre zipitérateur (au lieu de namedtuples), appelez:data_set.itertuples(index=False, name=None)
Axel
3
@coldspeed La leçon que j'ai tirée de la question liée est que itertuples est lent car la conversion en tuples est généralement plus lente que les opérations vectorisées / cython. Étant donné que la question demande de convertir en tuples, y a-t-il une raison pour laquelle nous pensons que la réponse acceptée est plus rapide? Le test rapide que j'ai fait indique que la version itertuples est plus rapide.
TC Proctor
2
J'ai publié mes résultats de test de vitesse dans cette réponse
TC Proctor
1
@johnDanger est similaire au concept de eval () et globals () en python. Tout le monde sait qu'ils existent. Tout le monde sait également que vous ne devriez généralement pas utiliser ces fonctions car elles sont considérées comme une mauvaise forme. Le principe ici est similaire, il y a très peu de cas d'utilisation de la famille iter * chez les pandas, c'est sans doute l'un d'entre eux. J'utiliserais toujours une méthode différente (comme une liste comp ou une carte) mais c'est moi.
cs95
45

Une manière générique:

[tuple(x) for x in data_set.to_records(index=False)]
Ramón J Romero y Vigil
la source
1
N'est-ce pas data_set.to_records(index=False).tolist()mieux?
Amir A. Shabani le
30

Motivation
De nombreux ensembles de données sont suffisamment volumineux pour que nous nous préoccupions de la vitesse / efficacité. Je propose donc cette solution dans cet esprit. Il se trouve que c'est aussi succinct.

Par souci de comparaison, supprimons la indexcolonne

df = data_set.drop('index', 1)

Solution
Je proposerai l'utilisation de zipetmap

list(zip(*map(df.get, df)))

[('2012-02-17', 24.75, 25.03),
 ('2012-02-16', 25.0, 25.07),
 ('2012-02-15', 24.99, 25.15),
 ('2012-02-14', 24.68, 25.05),
 ('2012-02-13', 24.62, 24.77),
 ('2012-02-10', 24.38, 24.61)]

Il se trouve être également flexible si nous voulions traiter un sous-ensemble spécifique de colonnes. Nous supposerons que les colonnes que nous avons déjà affichées sont le sous-ensemble que nous voulons.

list(zip(*map(df.get, ['data_date', 'data_1', 'data_2'])))

[('2012-02-17', 24.75, 25.03),
 ('2012-02-16', 25.0, 25.07),
 ('2012-02-15', 24.99, 25.15),
 ('2012-02-14', 24.68, 25.05),
 ('2012-02-13', 24.62, 24.77),
 ('2012-02-10', 24.38, 24.61)]

Qu'est-ce qui est plus rapide?

Turn's out recordsest le plus rapide suivi d'une convergence asymptotique zipmapetiter_tuples

J'utiliserai une bibliothèque simple_benchmarksque j'ai obtenue de ce post

from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()

import pandas as pd
import numpy as np

def tuple_comp(df): return [tuple(x) for x in df.to_numpy()]
def iter_namedtuples(df): return list(df.itertuples(index=False))
def iter_tuples(df): return list(df.itertuples(index=False, name=None))
def records(df): return df.to_records(index=False).tolist()
def zipmap(df): return list(zip(*map(df.get, df)))

funcs = [tuple_comp, iter_namedtuples, iter_tuples, records, zipmap]
for func in funcs:
    b.add_function()(func)

def creator(n):
    return pd.DataFrame({"A": random.randint(n, size=n), "B": random.randint(n, size=n)})

@b.add_arguments('Rows in DataFrame')
def argument_provider():
    for n in (10 ** (np.arange(4, 11) / 2)).astype(int):
        yield n, creator(n)

r = b.run()

Vérifiez les résultats

r.to_pandas_dataframe().pipe(lambda d: d.div(d.min(1), 0))

        tuple_comp  iter_namedtuples  iter_tuples   records    zipmap
100       2.905662          6.626308     3.450741  1.469471  1.000000
316       4.612692          4.814433     2.375874  1.096352  1.000000
1000      6.513121          4.106426     1.958293  1.000000  1.316303
3162      8.446138          4.082161     1.808339  1.000000  1.533605
10000     8.424483          3.621461     1.651831  1.000000  1.558592
31622     7.813803          3.386592     1.586483  1.000000  1.515478
100000    7.050572          3.162426     1.499977  1.000000  1.480131

r.plot()

entrez la description de l'image ici

piRSquared
la source
12

Voici une approche vectorisée (en supposant le dataframe, data_setà définir comme à la dfplace) qui retourne a listof tuplescomme indiqué:

>>> df.set_index(['data_date'])[['data_1', 'data_2']].to_records().tolist()

produit:

[(datetime.datetime(2012, 2, 17, 0, 0), 24.75, 25.03),
 (datetime.datetime(2012, 2, 16, 0, 0), 25.0, 25.07),
 (datetime.datetime(2012, 2, 15, 0, 0), 24.99, 25.15),
 (datetime.datetime(2012, 2, 14, 0, 0), 24.68, 25.05),
 (datetime.datetime(2012, 2, 13, 0, 0), 24.62, 24.77),
 (datetime.datetime(2012, 2, 10, 0, 0), 24.38, 24.61)]

L'idée de définir la colonne datetime comme axe d'index est d'aider à la conversion de la Timestampvaleur en son datetime.datetimeéquivalent de format correspondant en utilisant l' convert_datetime64argument dans DF.to_recordslequel le fait pour un DateTimeIndexdataframe.

Cela renvoie un recarrayqui pourrait alors être fait pour retourner un en listutilisant.tolist


Une solution plus généralisée selon le cas d'utilisation serait:

df.to_records().tolist()                              # Supply index=False to exclude index
Nickil Maveli
la source
10

Le moyen le plus efficace et le plus simple:

list(data_set.to_records())

Vous pouvez filtrer les colonnes dont vous avez besoin avant cet appel.

Gustavo Gonçalves
la source
2
Je pense que 'index = False' devrait être donné comme argument à to_records (). Ainsi, list (data_set.to_records (index = False))
user3415167
8

Cette réponse n'ajoute aucune réponse qui ne soit pas déjà discutée, mais voici quelques résultats rapides. Je pense que cela devrait résoudre les questions soulevées dans les commentaires. Tous ces éléments semblent être O (n) , sur la base de ces trois valeurs.

TL; DR : tuples = list(df.itertuples(index=False, name=None))et tuples = list(zip(*[df[c].values.tolist() for c in df]))sont à égalité pour les plus rapides.

J'ai fait un test de vitesse rapide sur les résultats pour trois suggestions ici:

  1. La réponse zip de @pirsquared: tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. La réponse acceptée de @ wes-mckinney: tuples = [tuple(x) for x in df.values]
  3. La réponse itertuples de @ksindi avec la name=Nonesuggestion de @Axel:tuples = list(df.itertuples(index=False, name=None))
from numpy import random
import pandas as pd


def create_random_df(n):
    return pd.DataFrame({"A": random.randint(n, size=n), "B": random.randint(n, size=n)})

Petite taille:

df = create_random_df(10000)
%timeit tuples = list(zip(*[df[c].values.tolist() for c in df]))
%timeit tuples = [tuple(x) for x in df.values]
%timeit tuples = list(df.itertuples(index=False, name=None))

Donne:

1.66 ms ± 200 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
15.5 ms ± 1.52 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.74 ms ± 75.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Plus grand:

df = create_random_df(1000000)
%timeit tuples = list(zip(*[df[c].values.tolist() for c in df]))
%timeit tuples = [tuple(x) for x in df.values]
%timeit tuples = list(df.itertuples(index=False, name=None))

Donne:

202 ms ± 5.91 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
1.52 s ± 98.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
209 ms ± 11.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Autant de patience que moi:

df = create_random_df(10000000)
%timeit tuples = list(zip(*[df[c].values.tolist() for c in df]))
%timeit tuples = [tuple(x) for x in df.values]
%timeit tuples = list(df.itertuples(index=False, name=None))

Donne:

1.78 s ± 118 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
15.4 s ± 222 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.68 s ± 96.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

La version zip et la version itertuples sont dans les intervalles de confiance l'une de l'autre. Je soupçonne qu'ils font la même chose sous le capot.

Ces tests de vitesse ne sont probablement pas pertinents. Repousser les limites de la mémoire de mon ordinateur ne prend pas beaucoup de temps, et vous ne devriez vraiment pas faire cela sur un grand ensemble de données. Travailler avec ces tuples après avoir fait cela finira par être vraiment inefficace. Il est peu probable que ce soit un goulot d'étranglement majeur dans votre code, alors tenez-vous-en à la version que vous pensez la plus lisible.

TC Proctor
la source
J'ai mis à jour mon message périmé. J'utilisais depuis un certain [*zip(*map(df.get, df))]temps maintenant. Quoi qu'il en soit, j'ai pensé que tu trouverais ça intéressant.
piRSquared
@piRSquared Oooh. J'aime la jolie intrigue. Je suppose que cela ressemble à O (n) .
TC Proctor
2
#try this one:

tuples = list(zip(data_set["data_date"], data_set["data_1"],data_set["data_2"]))
print (tuples)
Alsphere
la source
2

Changement de la liste des cadres de données en une liste de tuples.

df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [4, 5, 6]})
print(df)
OUTPUT
   col1  col2
0     1     4
1     2     5
2     3     6

records = df.to_records(index=False)
result = list(records)
print(result)
OUTPUT
[(1, 4), (2, 5), (3, 6)]
Gowtham Balusamy
la source
1
Veuillez ne pas publier uniquement le code comme réponse, mais également expliquer ce que fait votre code et comment il résout le problème de la question. Les réponses avec une explication sont généralement de meilleure qualité et sont plus susceptibles d'attirer des votes positifs.
Mark Rotteveel le
1

Plus de manière pythonique:

df = data_set[['data_date', 'data_1', 'data_2']]
map(tuple,df.values)
Ankur Panwar
la source
Une manière plus pythonique: l'exact opposé, en fait. map()est notoirement impythonique.
AMC le