Pandas convertem dataframe em matriz de tuplas

131

Manipulei alguns dados usando pandas e agora quero executar um salvamento em lote no banco de dados. Isso requer que eu converta o quadro de dados em uma matriz de tuplas, com cada tupla correspondendo a uma "linha" do quadro de dados.

Meu DataFrame se parece com:

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 

Eu quero convertê-lo em uma matriz de tuplas como:

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

Alguma sugestão de como posso fazer isso com eficiência?

enrishi
fonte
21
Para quem vem a esta resposta em 2017 ou mais, há uma nova solução idiomática abaixo . Você pode apenas usarlist(df.itertuples(index=False, name=None))
Ted Petrou 6/17
3
As duas coisas que estou procurando quando chego a esta pergunta: Uma lista de tuplas - df.to_records(index=False)e uma lista de ditados:df.to_dict('records')
Martin Thoma
@MartinThoma to_records e to_dict ('records') estragam meus tipos de dados. Bug conhecido, mas torna essas soluções inúteis ...
Jochen

Respostas:

206

E se:

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

para pandas <0,24 use

tuples = [tuple(x) for x in subset.values]
Wes McKinney
fonte
2
Por favor, veja a resposta de @ ksindi abaixo para usar .itertuples, que será mais eficiente do que obter os valores como uma matriz e transformá-los em uma tupla.
vy32
1
ligeiramente mais limpo é: tuplas = map (tupla, subset.values)
RufusVS
Isso pode converter valores para um tipo diferente, certo?
AMC
160
list(data_set.itertuples(index=False))

A partir de 17.1, o acima retornará uma lista de nomeados .

Se você quiser uma lista de tuplas comuns, passe name=Nonecomo argumento:

list(data_set.itertuples(index=False, name=None))
Kamil Sindi
fonte
39
Esta deve ser a resposta aceita IMHO (agora que existe um recurso dedicado). BTW, se você quiser tuples normais no seu zipiterador (em vez de namedtuples), chame:data_set.itertuples(index=False, name=None)
Axel
3
@coldspeed A lição que tirei da pergunta vinculada é que itertuples é lento porque a conversão para tuplas geralmente é mais lenta que as operações vetorizadas / cython. Dado que a pergunta está pedindo a conversão para tuplas, existe algum motivo para acharmos que a resposta aceita é mais rápida? O teste rápido que fiz indica que a versão itertuples é mais rápida.
TC Proctor
2
Postei meus resultados do teste de velocidade nesta resposta
TC Proctor
1
@johnDanger é semelhante ao conceito de eval () e globals () em python. Todo mundo sabe que eles existem. Todo mundo também sabe que você normalmente não deve usar essas funções porque é considerada uma forma incorreta. O princípio aqui é semelhante, existem muito poucos casos para usar a família iter * nos pandas, este é sem dúvida um deles. Eu ainda usaria um método diferente (como um comp de lista ou mapa), mas sou eu.
cs95
45

Uma maneira genérica:

[tuple(x) for x in data_set.to_records(index=False)]
Ramón J Romero e Vigília
fonte
1
Não é data_set.to_records(index=False).tolist()melhor?
Amir A. Shabani 04/04
30

Motivação
Muitos conjuntos de dados são grandes o suficiente para que possamos nos preocupar com velocidade / eficiência. Então, eu ofereço esta solução nesse espírito. Acontece também ser sucinto.

Para fins de comparação, vamos soltar a indexcoluna

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

Solução
vou propor o uso 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)]

Também é flexível se quisermos lidar com um subconjunto específico de colunas. Vamos assumir que as colunas que já exibimos são o 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)]

O que é mais rápido?

O resultado recordsé mais rápido, seguido de convergência assintoticamente zipmapeiter_tuples

Vou usar uma biblioteca simple_benchmarksque recebi deste 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()

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

insira a descrição da imagem aqui

piRSquared
fonte
12

Aqui está uma abordagem vetorizada (assumindo que o quadro data_setde dados seja definido como alternativa df) que retorna a listde tuplescomo mostrado:

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

produz:

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

A idéia de definir a coluna de data e hora como o eixo do índice é ajudar na conversão do Timestampvalor para o datetime.datetimeformato correspondente equivalente, usando o convert_datetime64argumento no DF.to_recordsqual o faz para um DateTimeIndexquadro de dados.

Isso retorna um recarrayque poderia ser feito para retornar um listuso.tolist


Uma solução mais generalizada, dependendo do caso de uso, seria:

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

A maneira mais eficiente e fácil:

list(data_set.to_records())

Você pode filtrar as colunas necessárias antes desta chamada.

Gustavo Gonçalves
fonte
1
Eu acho que 'index = False' deve ser dado como argumento para to_records (). Assim, list (data_set.to_records (index = False))
user3415167 19/04
8

Esta resposta não adiciona respostas que ainda não foram discutidas, mas aqui estão alguns resultados de velocidade. Eu acho que isso deve resolver as questões que surgiram nos comentários. Todos eles parecem O (n) , com base nesses três valores.

TL; DR : tuples = list(df.itertuples(index=False, name=None))e tuples = list(zip(*[df[c].values.tolist() for c in df]))estão empatados para o mais rápido.

Fiz um teste rápido de velocidade nos resultados para três sugestões aqui:

  1. A resposta zip de @pirsquared: tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. A resposta aceita de @ wes-mckinney: tuples = [tuple(x) for x in df.values]
  3. Os itertuples respondem de @ksindi com a name=Nonesugestão 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)})

Tamanho pequeno:

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)

Maior:

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)

Tanta paciência quanto eu tenho:

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)

A versão zip e a versão iteruples estão dentro dos intervalos de confiança entre si. Eu suspeito que eles estão fazendo a mesma coisa sob o capô.

Esses testes de velocidade provavelmente são irrelevantes. Aumentar os limites da memória do meu computador não leva muito tempo, e você realmente não deveria fazer isso em um grande conjunto de dados. Trabalhar com essas tuplas depois de fazer isso será realmente ineficiente. É improvável que seja um grande gargalo no seu código, então fique com a versão que você acha mais legível.

TC Proctor
fonte
Eu atualizei meu post antigo. Eu estava usando [*zip(*map(df.get, df))]há algum tempo agora. Enfim, achei que você acharia interessante.
amigos estão dizendo sobre piemonte
@piRSquared Oooh. Eu gosto do belo enredo. Eu acho que parece que é 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
fonte
2

Alterando a lista de quadros de dados em uma 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
fonte
1
Não publique apenas o código como resposta, mas também forneça uma explicação sobre o que o seu código faz e como ele resolve o problema da pergunta. As respostas com uma explicação geralmente são de qualidade superior e têm maior probabilidade de atrair votos positivos.
Mark Rotteveel
1

Maneira mais pitônica:

df = data_set[['data_date', 'data_1', 'data_2']]
map(tuple,df.values)
Ankur Panwar
fonte
Maneira mais pitônica: exatamente o oposto, na verdade. map()é notoriamente antitônico.
AMC