Pandas konvertieren Datenrahmen in Array von Tupeln


131

Ich habe einige Daten mit Pandas manipuliert und möchte jetzt eine Stapelspeicherung in der Datenbank durchführen. Dies erfordert, dass ich den Datenrahmen in ein Array von Tupeln konvertiere, wobei jedes Tupel einer "Zeile" des Datenrahmens entspricht.

Mein DataFrame sieht ungefähr so ​​aus:

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 

Ich möchte es in eine Reihe von Tupeln konvertieren wie:

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

Irgendwelche Vorschläge, wie ich das effizient machen kann?


21
Für diejenigen, die 2017+ zu dieser Antwort kommen, gibt es unten eine neue idiomatische Lösung . Sie können nur verwendenlist(df.itertuples(index=False, name=None))
Ted Petrou

3
Die zwei Dinge, nach denen ich suche, wenn ich zu dieser Frage komme: Eine Liste von Tupeln - df.to_records(index=False)und eine Liste von Diktaten:df.to_dict('records')
Martin Thoma

@MartinThoma sowohl to_records als auch to_dict ('records') schrauben meine Datentypen. Bekannter Fehler, aber macht diese Lösungen wertlos ...
Jochen

Antworten:


206

Wie wäre es mit:

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

für Pandas <0,24 verwenden

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

2
Die Verwendung von @ ksindi finden Sie weiter unten. Dies ist .itertupleseffizienter, als die Werte als Array abzurufen und in ein Tupel umzuwandeln.
vy32

1
etwas sauberer ist: tuples = map (tuple, subset.values)
RufusVS

Dies kann jedoch Werte in einen anderen Typ umwandeln, oder?
AMC

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

Ab 17.1 wird oben eine Liste der benannten Tupel zurückgegeben .

Wenn Sie eine Liste gewöhnlicher Tupel wünschen, übergeben Sie name=Noneals Argument:

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

39
Dies sollte meiner Meinung nach die akzeptierte Antwort sein (jetzt, da eine spezielle Funktion vorhanden ist). Übrigens, wenn Sie normale tuples in Ihrem zipIterator (anstelle von namedtuples) wollen, dann rufen Sie an:data_set.itertuples(index=False, name=None)
Axel


3
@coldspeed Die Lehre aus der verknüpften Frage ist, dass itertuples langsam ist, da das Konvertieren in Tupel normalerweise langsamer ist als vektorisierte / cython-Operationen. Gibt es einen Grund, warum wir glauben, dass die akzeptierte Antwort schneller ist, wenn man bedenkt, dass die Frage in Tupel konvertiert werden soll? Der schnelle Test, den ich durchgeführt habe, zeigt, dass die itertuples-Version schneller ist.
TC Proctor

2
Ich habe meine Geschwindigkeitstestergebnisse in dieser Antwort veröffentlicht
TC Proctor

1
@johnDanger ähnelt dem Konzept von eval () und globals () in Python. Jeder weiß, dass sie existieren. Jeder weiß auch, dass Sie diese Funktionen normalerweise nicht verwenden sollten, da dies als schlechte Form angesehen wird. Das Prinzip hier ist ähnlich, es gibt nur sehr wenige Fälle, in denen die iter * -Familie bei Pandas verwendet wird. Dies ist wohl einer von ihnen. Ich würde immer noch eine andere Methode verwenden (wie eine Listenkomposition oder eine Karte), aber das bin ich.
cs95


30

Motivation
Viele Datensätze sind groß genug, um uns mit Geschwindigkeit / Effizienz zu befassen. Deshalb biete ich diese Lösung in diesem Sinne an. Es ist auch kurz und bündig.

Lassen Sie uns zum Vergleich die indexSpalte fallen

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

Lösung
Ich werde die Verwendung von zipund vorschlagenmap

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

Es ist auch flexibel, wenn wir uns mit einer bestimmten Teilmenge von Spalten befassen möchten. Wir gehen davon aus, dass die Spalten, die wir bereits angezeigt haben, die gewünschte Teilmenge sind.

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

Was ist schneller?

Am recordsschnellsten stellt sich heraus, gefolgt von einer asymptotischen Konvergenzzipmap unditer_tuples

Ich werde eine Bibliothek verwenden simple_benchmarks, die ich aus diesem Beitrag erhalten habe

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

Überprüfen Sie die Ergebnisse

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

Geben Sie hier die Bildbeschreibung ein


12

Hier ist ein vektorisiert Ansatz (vorausgesetzt , den Datenrahmen, data_setwie definiert wird dfstatt) , dass die Renditen eines listvon tupleswie gezeigt:

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

produziert:

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

Die Idee, die Datum / Uhrzeit-Spalte als Indexachse festzulegen, besteht darin, die Konvertierung des TimestampWerts in das entsprechende datetime.datetimeFormat zu erleichtern, indem das convert_datetime64Argument verwendet wird, in DF.to_recordsdem dies für a giltDateTimeIndex erleichtern, Datenrahmen geschieht.

Dies gibt eine zurück, recarraydie dann dazu gebracht werden könnte, eine listVerwendung zurückzugeben.tolist


Eine allgemeinere Lösung in Abhängigkeit vom Anwendungsfall wäre:

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

10

Der effizienteste und einfachste Weg:

list(data_set.to_records())

Sie können die Spalten filtern, die Sie vor diesem Aufruf benötigen.


1
Ich denke, 'index = False' sollte als Argument für to_records () angegeben werden. Also list (data_set.to_records (index = False))
user3415167

8

Diese Antwort fügt keine Antworten hinzu, die noch nicht besprochen wurden, aber hier sind einige Geschwindigkeitsergebnisse. Ich denke, dies sollte Fragen lösen, die in den Kommentaren auftauchten. Alle diese Werte sehen aus wie O (n) , basierend auf diesen drei Werten.

TL; DR : tuples = list(df.itertuples(index=False, name=None))undtuples = list(zip(*[df[c].values.tolist() for c in df])) sind für die schnellsten gebunden.

Ich habe hier einen schnellen Geschwindigkeitstest mit den Ergebnissen für drei Vorschläge durchgeführt:

  1. Die Zip-Antwort von @pirsquared: tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. Die akzeptierte Antwort von @ wes-mckinney: tuples = [tuple(x) for x in df.values]
  3. Die itertuples Antwort von @ksindi mit dem name=NoneVorschlag von @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)})

Kleine Größe:

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

Gibt:

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)

Größer:

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

Gibt:

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)

So viel Geduld wie ich habe:

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

Gibt:

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)

Die Zip-Version und die Itertuples-Version liegen innerhalb der Konfidenzintervalle zueinander. Ich vermute, dass sie unter der Haube dasselbe tun.

Diese Geschwindigkeitstests sind jedoch wahrscheinlich irrelevant. Das Überschreiten der Speichergrenzen meines Computers nimmt nicht viel Zeit in Anspruch , und Sie sollten dies bei einem großen Datensatz wirklich nicht tun. Die Arbeit mit diesen Tupeln danach wird wirklich ineffizient sein. Es ist unwahrscheinlich, dass Ihr Code einen großen Engpass darstellt. Halten Sie sich also einfach an die Version, die Sie für am besten lesbar halten.


Ich habe meinen veralteten Beitrag aktualisiert. Ich hatte es schon seit einiger Zeit benutzt [*zip(*map(df.get, df))]. Ich dachte, du findest es interessant.
piRSquared

@piRSquared Oooh. Ich mag die hübsche Handlung. Ich denke, das sieht ungefähr so ​​aus, als wäre 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)

1

Mehr pythonischer Weg:

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

Mehr pythonischer Weg: Eigentlich genau das Gegenteil. map()ist notorisch unpythonisch.
AMC

1

Ändern der Datenrahmenliste in eine Liste von Tupeln.

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
Bitte posten Sie nicht nur Code als Antwort, sondern geben Sie auch eine Erklärung, was Ihr Code tut und wie er das Problem der Frage löst. Antworten mit einer Erklärung sind normalerweise von höherer Qualität und ziehen eher positive Stimmen an.
Mark Rotteveel
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.