Konwersja Django QuerySet do pandy DataFrame

90

Zamierzam przekonwertować zestaw Django QuerySet na pandy DataFramew następujący sposób:

qs = SomeModel.objects.select_related().filter(date__year=2012)
q = qs.values('date', 'OtherField')
df = pd.DataFrame.from_records(q)

Działa, ale czy istnieje bardziej efektywny sposób?

Franco Mariluis
źródło
Cześć @FrancoMariluis, przepraszam za to nie na temat: czy używasz pand w projektach django. Grafikę pokazujesz używając "Plotting with matplotlib" poprzez aplikacje webowe django. Czy to właściwe rozwiązanie dla Ciebie? Dzięki.
dani herrera
Cześć, do wyświetlania grafiki w Django używam django-chartit, który działa dobrze, ale myślę o użyciu matplotlib, co dałoby mi większą elastyczność
Franco Mariluis
Wygląda całkiem prosto i działa. Masz jakieś szczególne obawy?
Dmitrij Szewczenko
Co jest złego w sposobie, w jaki go teraz masz? Czy masz szczególne obawy?
Burhan Khalid,
To było moje pierwsze (i jedyne!) Podejście, ale ponieważ jestem dość nowy w pandach, chciałem sprawdzić, czy istnieje inny sposób, ale wydaje się, że jest dobry.
Franco Mariluis

Odpowiedzi:

87
import pandas as pd
import datetime
from myapp.models import BlogPost

df = pd.DataFrame(list(BlogPost.objects.all().values()))
df = pd.DataFrame(list(BlogPost.objects.filter(date__gte=datetime.datetime(2012, 5, 1)).values()))

# limit which fields
df = pd.DataFrame(list(BlogPost.objects.all().values('author', 'date', 'slug')))

Powyższe pokazuje, jak robię to samo. Najbardziej użytecznym dodatkiem jest określenie, które pola Cię interesują. Jeśli jest to tylko podzbiór dostępnych pól, którymi jesteś zainteresowany, to dałoby to wzrost wydajności, jak sobie wyobrażam.

leksykalny
źródło
37
Wydaje się, że użycie metody „list ()” jest przestarzałe (jestem na pandach 0.12). Korzystanie DataFrame.from_records()działa lepiej, tj df = pd.DataFrame.from_records(BlogPost.objects.all().values()).
gregoltsov
2
Byłoby bardziej jasne, gdyby użyło nazw z pytania PO. Na przykład BlogPostma być taki sam jak jego SomeModel?
Hack-R
Cześć, czy istnieje sposób na wykluczenie kolumny, której nie potrzebujesz w ramce danych?
Willower
19

Django Pandas rozwiązuje to dość dobrze: https://github.com/chrisdev/django-pandas/

Z README:

class MyModel(models.Model):
    full_name = models.CharField(max_length=25)
    age = models.IntegerField()
    department = models.CharField(max_length=3)
    wage = models.FloatField()

from django_pandas.io import read_frame
qs = MyModel.objects.all()
df = read_frame(qs)
David Watson
źródło
10
Jak Django Pandas radzi sobie z dużymi zbiorami danych? github.com/chrisdev/django-pandas/blob/master/django_pandas/… Ta linia mnie przeraża, ponieważ myślę, że oznacza to, że cały zestaw danych zostanie załadowany do pamięci na raz.
Adam Barnes
@Ada Aby utworzyć ramkę danych przy użyciu określonych nazw pól:df = read_frame(qs, fieldnames=['age', 'wage', 'full_name'])
Gathide
Dla tych z was w tej cudownej przyszłości, którzy zastanawiają się, o czym mówiłem, oto bardziej trwały link do źródła w tym czasie: github.com/chrisdev/django-pandas/blob/ ...
Adam Barnes
9

Konwersja zestawu zapytań na values_list () będzie bardziej wydajna w pamięci niż bezpośrednio na wartości (). Ponieważ metoda values ​​() zwraca zestaw zapytań zawierający listę dict (pary klucz: wartość), lista_wartości () zwraca tylko listę krotek (czyste dane). Zaoszczędzi to około 50% pamięci, wystarczy ustawić informacje o kolumnie podczas wywoływania pd.DataFrame ().

Metoda 1:
    queryset = models.xxx.objects.values ​​(„A”, „B”, „C”, „D”)
    df = pd.DataFrame (list (queryset)) ## zużywa dużo pamięci
    #df = pd.DataFrame.from_records (queryset) ## działa, ale bez większych zmian w zużyciu pamięci

Metoda 2:
    queryset = models.xxx.objects.values_list („A”, „B”, „C”, „D”)
    df = pd.DataFrame (list (queryset), columns = ["A", "B", "C", "D"]) ## to zaoszczędzi 50% pamięci
    #df = pd.DataFrame.from_records (queryset, columns = [„A”, „B”, „C”, „D”]) ## To nie działa. Awaria z typem danych to queryset, a nie lista.

Przetestowałem to w moim projekcie z danymi> 1 miliona wierszy, pamięć szczytowa została zmniejszona z 2G do 1G.

shengyang wang
źródło
2

Z perspektywy Django (nie jestem zaznajomiony pandas) to jest w porządku. Moim jedynym zmartwieniem jest to, że jeśli masz bardzo dużą liczbę rekordów, możesz napotkać problemy z pamięcią. Gdyby tak było, konieczne byłoby coś na wzór tego iteratora zestawu zapytań wydajnego pod względem pamięci . (Napisany fragment może wymagać przepisania, aby umożliwić inteligentne użycie .values()).

David Eyk
źródło
Pomysł @ GregoryGoltsov, aby używać .from_records()i nie używać list(), wyeliminuje problem z wydajnością pamięci.
płyty grzewcze
1
Problem wydajności pamięci jest po stronie Django. .values()zwraca wartość, ValuesQuerySetktóra buforuje wyniki, więc dla wystarczająco dużego zbioru danych będzie wymagała dużej ilości pamięci.
David Eyk
1
Ach tak. Będziesz musiał indeksować zestaw zapytań i używać go .from_recordsbez rozumienia listy, aby wyeliminować obie świnie pamięci. np pd.DataFrame.from_records(qs[i].__dict__ for i in range(qs.count())). Ale kiedy skończysz, zostajesz z tą irytującą "_state"kolumną. qs.values()[i]jest znacznie szybszy i czystszy, ale myślę, że buforuje.
płyty grzejne
1

Możesz użyć model_to_dict

import datetime
from django.forms import model_to_dict
pallobjs = [ model_to_dict(pallobj) for pallobj in PalletsManag.objects.filter(estado='APTO_PARA_VENTA')] 
df = pd.DataFrame(pallobjs)
df.head()
Pjl
źródło