I panda convertono il frame di dati in array di tuple


132

Ho manipolato alcuni dati utilizzando Panda e ora voglio eseguire un salvataggio in batch nel database. Ciò richiede che io converta il frame di dati in una matrice di tuple, con ogni tupla corrispondente a una "riga" del frame di dati.

Il mio DataFrame è simile a:

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 

Voglio convertirlo in una matrice di tuple come:

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

Qualche suggerimento su come posso farlo in modo efficiente?


21
Per coloro che arrivano a questa risposta nel 2017+, c'è una nuova soluzione idiomatica di seguito . Puoi semplicemente usarelist(df.itertuples(index=False, name=None))
Ted Petrou il

3
Le due cose che sto cercando quando arrivo a questa domanda: un elenco di tuple - df.to_records(index=False)e un elenco di dicts:df.to_dict('records')
Martin Thoma,

@MartinThoma to_records e to_dict ('records') rovinano i miei tipi di dati. Bug noto ma rende inutili queste soluzioni ...
Jochen

Risposte:


206

Che ne dite di:

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

per panda <0,24 usare

tuples = [tuple(x) for x in subset.values]

2
Di seguito, vedi la risposta di @ ksindi per l'utilizzo .itertuples, che sarà più efficiente che ottenere i valori come un array e trasformarli in una tupla.
vy32,

1
leggermente più pulito è: tuple = map (tuple, subset.values)
RufusVS

Questo può però lanciare valori su un tipo diverso, giusto?
AMC

161
list(data_set.itertuples(index=False))

A partire da 17.1, quanto sopra restituirà un elenco di coppie di nomi .

Se vuoi un elenco di tuple ordinarie, passa name=Nonecome argomento:

list(data_set.itertuples(index=False, name=None))

39
Questa dovrebbe essere la risposta accettata IMHO (ora che esiste una funzione dedicata). A proposito, se vuoi normali tuples nel tuo zipiteratore (anziché namedtuples), chiama:data_set.itertuples(index=False, name=None)
Axel


3
@coldspeed La lezione che ho tratto dalla domanda collegata è che itertuples è lento perché la conversione in tuple è generalmente più lenta delle operazioni vectorized / cython. Dato che la domanda è chiedere di convertirsi in tuple, c'è qualche ragione per pensare che la risposta accettata sia più veloce? Il test rapido che ho fatto indica che la versione itertuples è più veloce.
TC Proctor

2
Ho pubblicato i risultati dei miei test di velocità in questa risposta
TC Proctor

1
@johnDanger è simile al concetto di eval () e globals () in Python. Tutti sanno che esistono. Tutti sanno anche che in genere non dovresti usare queste funzioni perché è considerata una cattiva forma. Il principio qui è simile, ci sono pochissimi casi per usare la famiglia iter * in panda, questo è probabilmente uno di questi. Userei ancora un metodo diverso (come un elenco comp o una mappa) ma sono io.
cs95,


30

Motivazione
Molti insiemi di dati sono abbastanza grandi che dobbiamo preoccuparci della velocità / efficienza. Quindi offro questa soluzione con questo spirito. Capita anche di essere succinta.

Per motivi di confronto, lasciamo cadere la indexcolonna

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

Soluzione
Proporrò l'uso di zipemap

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)]

Capita anche di essere flessibile se volessimo occuparci di un sottoinsieme specifico di colonne. Supponiamo che le colonne che abbiamo già visualizzato siano il sottoinsieme che vogliamo.

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)]

Che cos'è Quicker?

Il risultato recordsè il più veloce seguito dalla convergenza asintotica zipmapeiter_tuples

Userò una libreria simple_benchmarksche ho ottenuto da questo 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()

Controlla i risultati

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()

inserisci qui la descrizione dell'immagine


12

Ecco un approccio vettoriale (assumendo il frame di dati, data_setda definire come dfinvece) che restituisce un listdi tuplescome mostrato:

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

produce:

[(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'idea di impostare la colonna datetime come asse dell'indice è di aiutare nella conversione del Timestampvalore nel corrispondente datetime.datetimeformato equivalente usando l' convert_datetime64argomento in DF.to_recordscui lo fa per un DateTimeIndexframe di dati.

Questo restituisce un recarrayche potrebbe quindi essere fatto per restituire un listutilizzo.tolist


Una soluzione più generalizzata a seconda del caso d'uso sarebbe:

df.to_records().tolist()                              # Supply index=False to exclude index

10

Il modo più efficiente e semplice:

list(data_set.to_records())

Puoi filtrare le colonne necessarie prima di questa chiamata.


1
Penso che 'index = False' dovrebbe essere dato come argomento a to_records (). Pertanto, list (data_set.to_records (index = False))
user3415167

8

Questa risposta non aggiunge alcuna risposta che non sia già stata discussa, ma qui ci sono alcuni risultati sulla velocità. Penso che questo dovrebbe risolvere le domande che sono emerse nei commenti. Tutti questi sembrano essere O (n) , in base a questi tre valori.

TL; DR : tuples = list(df.itertuples(index=False, name=None))e tuples = list(zip(*[df[c].values.tolist() for c in df]))sono legati per il più veloce.

Ho fatto un rapido test di velocità sui risultati per tre suggerimenti qui:

  1. La risposta zip di @pirsquared: tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. La risposta accettata da @ wes-mckinney: tuples = [tuple(x) for x in df.values]
  3. La risposta itertuples di @ksindi con il name=Nonesuggerimento di @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)})

Taglia piccola:

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))

dà:

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)

Più grandi:

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))

dà:

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)

Quanta pazienza ho:

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))

dà:

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 versione zip e la versione itertuples sono reciprocamente all'interno degli intervalli di confidenza. Sospetto che stiano facendo la stessa cosa sotto il cofano.

Tuttavia, questi test di velocità sono probabilmente irrilevanti. Superare i limiti della memoria del mio computer non prende una quantità enorme di tempo, e in realtà non dovrebbe fare questo su un grande insieme di dati. Lavorare con quelle tuple dopo aver fatto questo finirà per essere davvero inefficiente. È improbabile che si verifichi un grosso collo di bottiglia nel tuo codice, quindi mantieni la versione che ritieni più leggibile.


Ho aggiornato il mio post stantio. Lo usavo [*zip(*map(df.get, df))]da qualche tempo. Comunque, ho pensato che l'avresti trovato interessante.
piRSquared

@piRSquared Oooh. Mi piace la trama carina. Immagino che sembri 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)

2

Modifica dell'elenco dei frame di dati in un elenco di tuple.

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)]

1
Si prega di non pubblicare solo il codice come risposta, ma anche di fornire una spiegazione su cosa fa il codice e su come risolve il problema della domanda. Le risposte con una spiegazione sono generalmente di qualità superiore e hanno maggiori probabilità di attirare voti positivi.
Mark Rotteveel,

1

Modo più pitonico:

df = data_set[['data_date', 'data_1', 'data_2']]
map(tuple,df.values)

Modo più pitonico: l'esatto contrario, in realtà. map()è notoriamente non pitonico.
AMC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.