Pandy konwertują ramkę danych na tablicę krotek


148

Manipulowałem niektórymi danymi za pomocą pand, a teraz chcę wykonać zbiorczy zapis z powrotem do bazy danych. Wymaga to ode mnie przekształcenia ramki danych w tablicę krotek, z których każda odpowiada „wierszowi” ramki danych.

Moja DataFrame wygląda mniej więcej tak:

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 

Chcę przekonwertować go na tablicę krotek, takich jak:

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

Jakieś sugestie, jak mogę to skutecznie zrobić?


25
Dla tych, którzy przyjdą na tę odpowiedź w 2017+, poniżej znajduje się nowe idiomatyczne rozwiązanie . Możesz po prostu użyćlist(df.itertuples(index=False, name=None))
Ted Petrou,

3
Dwie rzeczy, których szukam, kiedy dochodzę do tego pytania: lista krotek - df.to_records(index=False)i lista dyktowanych:df.to_dict('records')
Martin Thoma

@MartinThoma i to_records i to_dict ('rekordy') pieprzą moje typy danych. Znany błąd, ale czyni to rozwiązanie bezwartościowym ...
Jochen

Odpowiedzi:


214

Co powiesz na:

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

dla pand <0,24 użyj

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

4
Zobacz odpowiedź @ ksindi poniżej na temat używania .itertuples, które będzie bardziej wydajne niż pobieranie wartości jako tablicy i umieszczanie ich w krotce.
vy32

1
nieco czystszy jest: krotki = mapa (krotka, podzbiór.wartości)
RufusVS

Może to jednak rzutować wartości na inny typ, prawda?
AMC

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

Od 17.1 powyższe zwróci listę nazwanych krotek .

Jeśli chcesz listę zwykłych krotek, podaj name=Nonejako argument:

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

41
Powinna to być akceptowana odpowiedź IMHO (teraz, gdy istnieje dedykowana funkcja). BTW, jeśli chcesz normalne tuples w swoim zipiteratorze (zamiast namedtuples), zadzwoń:data_set.itertuples(index=False, name=None)
Axel


3
@coldspeed Lekcja, którą wyciągnąłem z połączonego pytania, jest taka, że ​​itertuples jest powolny, ponieważ konwersja do krotek jest zwykle wolniejsza niż operacje wektoryzowane / cython. Biorąc pod uwagę, że pytanie dotyczy konwersji na krotki, czy jest jakiś powód, dla którego moglibyśmy sądzić, że zaakceptowana odpowiedź jest szybsza? Szybki test, który przeprowadziłem, wskazuje, że wersja itertuples jest szybsza.
TC Proctor


1
@johnDanger jest podobny do koncepcji eval () i globals () w Pythonie. Wszyscy wiedzą, że istnieją. Wszyscy wiedzą również, że zazwyczaj nie powinieneś używać tych funkcji, ponieważ jest to uważane za złą formę. Zasada jest podobna, jest bardzo niewiele przypadków użycia rodziny iter * w pandach, prawdopodobnie jest to jeden z nich. Nadal używałbym innej metody (np. Zestawienia listy lub mapy), ale to ja.
cs95


33

Motywacja
Wiele zestawów danych jest na tyle dużych, że musimy zająć się szybkością / wydajnością. Dlatego proponuję to rozwiązanie w tym duchu. Bywa też zwięzły.

Dla porównania upuśćmy indexkolumnę

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

Rozwiązanie
Zaproponuję użycie zipimap

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

Zdarza się również, że jest elastyczny, jeśli chcemy zajmować się określonym podzbiorem kolumn. Zakładamy, że kolumny, które już wyświetliliśmy, są podzbiorem, którego potrzebujemy.

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

Co to jest szybsze?

recordsNajszybciej następuje wyjście, po którym następuje asymptotyczna zbieżność zipmapiiter_tuples

Skorzystam z biblioteki simple_benchmarks, którą otrzymałem z tego postu

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

Sprawdź wyniki

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

wprowadź opis obrazu tutaj


12

Oto podejście wektoryzowane (zakładając ramkę danych, którą data_setnależy zdefiniować jako df), które zwraca wartość listz, tuplesjak pokazano:

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

produkuje:

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

Ideą ustawienia kolumny datetime jako osi indeksu jest pomoc w konwersji Timestampwartości do odpowiadającego jej odpowiednika datetime.datetimeformatu poprzez użycie convert_datetime64argumentu, w DF.to_recordsktórym robi to dla DateTimeIndexramki danych.

Zwraca to a, recarrayktóre można następnie zmusić do zwrócenia listusing.tolist


Bardziej uogólnionym rozwiązaniem w zależności od przypadku użycia byłoby:

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

10

Najbardziej wydajny i łatwy sposób:

list(data_set.to_records())

Przed wywołaniem możesz filtrować potrzebne kolumny.


2
Myślę, że „index = False” powinno być podane jako argument funkcji to_records (). Zatem list (data_set.to_records (index = False))
user3415167

8

Ta odpowiedź nie dodaje żadnych odpowiedzi, które nie zostały jeszcze omówione, ale oto kilka wyników dotyczących szybkości. Myślę, że to powinno rozwiązać pytania, które pojawiły się w komentarzach. Wszystkie wyglądają, jakby były O (n) , na podstawie tych trzech wartości.

TL; DR : tuples = list(df.itertuples(index=False, name=None))i tuples = list(zip(*[df[c].values.tolist() for c in df]))remisują za najszybszą.

Wykonałem szybki test szybkości wyników dla trzech sugestii tutaj:

  1. Odpowiedź zip z @pirsquared: tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. Zaakceptowana odpowiedź od @ wes-mckinney: tuples = [tuple(x) for x in df.values]
  3. Odpowiedź iterkułów z @ksindi z name=Nonesugestią @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)})

Mały rozmiar:

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

Daje:

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)

Większy:

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

Daje:

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)

Tyle cierpliwości, ile mam:

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

Daje:

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)

Wersja zip i wersja itertuples mieszczą się w przedziałach ufności. Podejrzewam, że robią to samo pod maską.

Te testy prędkości są jednak prawdopodobnie nieistotne. Przekraczanie granic pamięci mojego komputera nie zajmuje dużo czasu i naprawdę nie powinno się tego robić na dużym zestawie danych. Praca z tymi krotkami po wykonaniu tej czynności będzie naprawdę nieefektywna. Jest mało prawdopodobne, aby stanowił on główne wąskie gardło w kodzie, więc po prostu trzymaj się wersji, którą uważasz za najbardziej czytelną.


Zaktualizowałem mój stary post. Używałem już od jakiegoś [*zip(*map(df.get, df))]czasu. W każdym razie pomyślałem, że uznasz to za interesujące.
piRSquared

@piRSquared Oooh. Podoba mi się ładna fabuła. Wydaje mi się, że to wygląda na to, że to O (n) .
TC Proctor

3

Zmiana listy ramek danych na listę krotek.

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
Nie wysyłaj tylko kodu jako odpowiedzi, ale także wyjaśnij, co robi twój kod i jak rozwiązuje problem pytania. Odpowiedzi z wyjaśnieniem są zwykle wyższej jakości i częściej przyciągają głosy za.
Mark Rotteveel

2
#try this one:

tuples = list(zip(data_set["data_date"], data_set["data_1"],data_set["data_2"]))
print (tuples)

1

Bardziej pythonowy sposób:

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

Bardziej pytoniczny sposób: właściwie dokładnie odwrotnie. map()jest notorycznie nieszablonowy.
AMC
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.