Los pandas convierten el marco de datos en una matriz de tuplas

131

He manipulado algunos datos usando pandas y ahora quiero llevar a cabo un lote de nuevo en la base de datos. Esto requiere que convierta el marco de datos en una matriz de tuplas, con cada tupla correspondiente a una "fila" del marco de datos.

Mi DataFrame se parece 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 

Quiero convertirlo en una serie de tuplas como:

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

¿Alguna sugerencia sobre cómo puedo hacer esto de manera eficiente?

enrishi
fuente
21
Para aquellos que lleguen a esta respuesta en 2017+, hay una nueva solución idiomática a continuación . Puedes usarlist(df.itertuples(index=False, name=None))
Ted Petrou el
3
Las dos cosas que busco cuando llego a esta pregunta: una lista de tuplas df.to_records(index=False)y una lista de dictados:df.to_dict('records')
Martin Thoma
@MartinThoma tanto to_records como to_dict ('records') atornillan mis tipos de datos. Error conocido pero hace que estas soluciones no tengan valor ...
Jochen

Respuestas:

206

Qué tal si:

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

para pandas <0.24 uso

tuples = [tuple(x) for x in subset.values]
Wes McKinney
fuente
2
Consulte la respuesta de @ ksindi a continuación para usarla .itertuples, que será más eficiente que obtener los valores como una matriz y convertirlos en una tupla.
vy32
1
un poco más limpio es: tuplas = map (tuple, subset.values)
RufusVS
Sin embargo, esto puede emitir valores a un tipo diferente, ¿verdad?
AMC
160
list(data_set.itertuples(index=False))

A partir del 17.1, lo anterior devolverá una lista de tuplas nombradas .

Si desea una lista de tuplas ordinarias, pase name=Nonecomo argumento:

list(data_set.itertuples(index=False, name=None))
Kamil Sindi
fuente
39
Esta debería ser la respuesta aceptada en mi humilde opinión (ahora que existe una función dedicada). Por cierto, si desea mensajes de correo electrónico normales tupleen su zipiterador (en lugar de namedtuples), llame al:data_set.itertuples(index=False, name=None)
Axel
3
@coldspeed La lección que obtuve de la pregunta vinculada es que las itertuples son lentas porque la conversión a tuplas suele ser más lenta que las operaciones vectorizadas / cython. Dado que la pregunta es convertir a tuplas, ¿hay alguna razón para pensar que la respuesta aceptada es más rápida? La prueba rápida que hice indica que la versión de itertuples es más rápida.
TC Proctor
2
Publiqué los resultados de mi prueba de velocidad en esta respuesta
TC Proctor
1
@johnDanger es similar al concepto de eval () y globals () en python. Todos saben que existen. Todo el mundo también sabe que normalmente no deberías usar estas funciones porque se considera una mala forma. El principio aquí es similar, hay muy pocos casos para usar la familia iter * en los pandas, este es posiblemente uno de ellos. Todavía usaría un método diferente (como una lista de compilación o un mapa) pero ese soy yo.
cs95
45

Una forma genérica:

[tuple(x) for x in data_set.to_records(index=False)]
Ramón J Romero y Vigil
fuente
1
No es data_set.to_records(index=False).tolist()mejor?
Amir A. Shabani
30

Motivación
Muchos conjuntos de datos son lo suficientemente grandes como para que tengamos que preocuparnos por la velocidad / eficiencia. Entonces ofrezco esta solución con ese espíritu. Sucede que también es sucinto.

En aras de la comparación, dejemos caer la indexcolumna

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

Solución
Propondré el uso de zipymap

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

Resulta que también es flexible si quisiéramos tratar con un subconjunto específico de columnas. Asumiremos que las columnas que ya hemos mostrado son el subconjunto que queremos.

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é es más rápido?

El recordsdesvío es más rápido seguido de convergencia asintótica zipmapyiter_tuples

Usaré una biblioteca simple_benchmarksque obtuve de esta publicación

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

Comprueba los resultados

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

ingrese la descripción de la imagen aquí

piRSquared
fuente
12

Aquí hay un enfoque vectorizado (asumiendo el marco de datos, data_setque se definirá como en su dflugar) que devuelve un listde tuplescomo se muestra:

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

La idea de establecer la columna de fecha y hora como el eje del índice es ayudar en la conversión del Timestampvalor a su datetime.datetimeequivalente de formato correspondiente haciendo uso del convert_datetime64argumento en el DF.to_recordsque lo hace para un DateTimeIndexmarco de datos.

Esto devuelve un recarrayque luego podría hacerse para devolver un listuso.tolist


La solución más generalizada según el caso de uso sería:

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

La forma más eficiente y fácil:

list(data_set.to_records())

Puede filtrar las columnas que necesita antes de esta llamada.

Gustavo Gonçalves
fuente
1
Creo que 'index = False' debería darse como argumento para to_records (). Por lo tanto, list (data_set.to_records (index = False))
user3415167
8

Esta respuesta no agrega ninguna respuesta que aún no se haya discutido, pero aquí hay algunos resultados de velocidad. Creo que esto debería resolver las preguntas que surgieron en los comentarios. Todos estos parecen ser O (n) , basados ​​en estos tres valores.

TL; DR : tuples = list(df.itertuples(index=False, name=None))y tuples = list(zip(*[df[c].values.tolist() for c in df]))están empatados para el más rápido.

Hice una prueba de velocidad rápida en los resultados de tres sugerencias aquí:

  1. La respuesta zip de @pirsquared: tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. La respuesta aceptada de @ wes-mckinney: tuples = [tuple(x) for x in df.values]
  3. Los itertuples responden de @ksindi con la name=Nonesugerencia 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)})

Talla pequeña:

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

Da:

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)

Más grande:

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

Da:

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)

Tanta paciencia como tengo:

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

Da:

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 versión zip y la versión itertuples están dentro de los intervalos de confianza entre sí. Sospecho que están haciendo lo mismo debajo del capó.

Sin embargo, estas pruebas de velocidad son probablemente irrelevantes. Llevar los límites de la memoria de mi computadora no lleva una gran cantidad de tiempo, y realmente no deberías estar haciendo esto en un gran conjunto de datos. Trabajar con esas tuplas después de hacer esto terminará siendo realmente ineficiente. Es poco probable que sea un cuello de botella importante en su código, por lo tanto, quédese con la versión que cree que es más legible.

TC Proctor
fuente
Actualicé mi publicación obsoleta. Había estado usando [*zip(*map(df.get, df))]por algún tiempo ahora. De todos modos, pensé que lo encontraría interesante.
piRSquared el
@piRSquared Oooh. Me gusta la bonita trama. Supongo que parece que es 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
fuente
2

Cambiar la lista de marcos de datos en una lista de tuplas.

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
fuente
1
No publique solo el código como respuesta, sino que también brinde una explicación de lo que hace su código y cómo resuelve el problema de la pregunta. Las respuestas con una explicación suelen ser de mayor calidad y es más probable que atraigan votos positivos.
Mark Rotteveel
1

Más forma pitónica:

df = data_set[['data_date', 'data_1', 'data_2']]
map(tuple,df.values)
Ankur Panwar
fuente
Forma más pitónica: todo lo contrario, en realidad. map()Es notoriamente no pitónico.
AMC