2016-04-19 18 views
8

UWAGA: szuka pomocy w skuteczny sposób to zrobić oprócz mega dołączyć, a następnie obliczenie różnicy między datamiPandy - SQL sprawa rachunku równowartość

mam table1 identyfikator kraju i datę (bez duplikaty tych wartości) i chcę podsumować informację o numerze table2 (która ma kraj, datę, klastel_x i zmienną count, gdzie klastel_x jest klastrem_1, klastrem_2, klastrem_3), tak aby table1 dołączyła do niego każdą wartość identyfikatora klastra i podsumowanie liczyć od table2, gdzie data od table2 wystąpił w ciągu 30 dni przed datą w table1.

Wierzę, że to jest proste w SQL: jak to zrobić w Pandach?

select a.date,a.country, 
sum(case when a.date - b.date between 1 and 30 then b.cluster_1 else 0 end) as cluster1, 
sum(case when a.date - b.date between 1 and 30 then b.cluster_2 else 0 end) as cluster2, 
sum(case when a.date - b.date between 1 and 30 then b.cluster_3 else 0 end) as cluster3 

from table1 a 
left outer join table2 b 
on a.country=b.country 

group by a.date,a.country 

EDIT:

Oto nieco zmienionym przykładem. Powiedzmy, że jest to tabela 1, zagregowany zbiór danych z datą, miastem, klastrem i liczbą. Poniżej znajduje się zestaw danych "zapytanie" (tabela 2). w tym przypadku chcemy zsumować pole licznika z tabeli1 dla klastra1, klastra2, klastra3 (jest ich w rzeczywistości 100) odpowiadających identyfikatorowi kraju, o ile pole daty w tabeli1 znajduje się w ciągu 30 dni wcześniej.

Na przykład pierwszy wiersz zestawu danych zapytania ma datę 2/2/2015 i kraj 1. W tabeli 1 istnieje tylko jeden wiersz w ciągu 30 dni wcześniej i jest on dla klastra 2 z liczbą 2.

enter image description here

Oto zrzut z dwóch tabel w pliku CSV:

date,country,cluster,count 
2014-01-30,1,1,1 
2015-02-03,1,1,3 
2015-01-30,1,2,2 
2015-04-15,1,2,5 
2015-03-01,2,1,6 
2015-07-01,2,2,4 
2015-01-31,2,3,8 
2015-01-21,2,1,2 
2015-01-21,2,1,3 

i Tabela 2:

date,country 
2015-02-01,1 
2015-04-21,1 
2015-02-21,2 
+0

mógłbyś dodawać zestawy przykładowe dane _wejście_ (5-7 wierszy w CSV/dict/JSON/python kod formatu __as text__, więc możemy użyć to podczas kodowania)? [Jak utworzyć przykład minimalny, pełny i sprawdzalny] (http://stackoverflow.com/help/mcve) – MaxU

+0

Hows, że MaxU? –

+0

jest teraz znacznie lepiej, ale zmieniłeś algorytm - czy chcesz sumować 'cluster_X' z' table2' lub 'count' z' table1'? Czy możesz również opublikować pożądany wynik? – MaxU

Odpowiedz

1

Edytuj: Oop - żałuję, że nie widziałem tej zmiany o dołączaniu przed przesłaniem. Np, zostawię to, bo to była dobra zabawa. Krytyki mile widziane.

Gdzie table1 i table2 znajdują się w tym samym katalogu, co ten skrypt w "table1.csv" i "table2.csv", to powinno działać.

nie uzyskać ten sam rezultat jak swoimi przykładami z 30 dni - musiał zderzyć go do 31 dni, ale myślę, że duch jest tutaj:

import pandas as pd 
import numpy as np 

table1_path = './table1.csv' 
table2_path = './table2.csv' 

with open(table1_path) as f: 
    table1 = pd.read_csv(f) 
table1.date = pd.to_datetime(table1.date) 

with open(table2_path) as f: 
    table2 = pd.read_csv(f) 
table2.date = pd.to_datetime(table2.date) 

joined = pd.merge(table2, table1, how='outer', on=['country']) 

joined['datediff'] = joined.date_x - joined.date_y 

filtered = joined[(joined.datediff >= np.timedelta64(1, 'D')) & (joined.datediff <= np.timedelta64(31, 'D'))] 

gb_date_x = filtered.groupby(['date_x', 'country', 'cluster']) 

summed = pd.DataFrame(gb_date_x['count'].sum()) 

result = summed.unstack() 
result.reset_index(inplace=True) 
result.fillna(0, inplace=True) 

Moja moc testu:

ipdb> table1 
       date country cluster count 
0 2014-01-30 00:00:00  1  1  1 
1 2015-02-03 00:00:00  1  1  3 
2 2015-01-30 00:00:00  1  2  2 
3 2015-04-15 00:00:00  1  2  5 
4 2015-03-01 00:00:00  2  1  6 
5 2015-07-01 00:00:00  2  2  4 
6 2015-01-31 00:00:00  2  3  8 
7 2015-01-21 00:00:00  2  1  2 
8 2015-01-21 00:00:00  2  1  3 
ipdb> table2 
       date country 
0 2015-02-01 00:00:00  1 
1 2015-04-21 00:00:00  1 
2 2015-02-21 00:00:00  2 

...

ipdb> result 
        date_x country count 
cluster         1 2 3 
0  2015-02-01 00:00:00  1  0 2 0 
1  2015-02-21 00:00:00  2  5 0 8 
2  2015-04-21 00:00:00  1  0 5 0 
0

UPDATE:

Myślę, że nie ma sensu używać pand do przetwarzania danych, które nie mieszczą się w Twojej pamięci. Oczywiście są pewne sztuczki, jak sobie z tym poradzić, ale jest to bolesne.

Jeśli chcesz efektywnie przetwarzać dane, użyj odpowiedniego narzędzia.

Polecam, aby bliżej przyjrzeć się Apache Spark SQL, gdzie można przetwarzać dane rozproszone na wielu węzłach klastra, przy użyciu znacznie większej ilości pamięci/mocy przetwarzania/IO/etc. w porównaniu do podsystemu komputer/IO podsystemu/procesora CPU.

Alternatywnie można spróbować użytku jak RDBMS Oracle DB (bardzo drogiego, zwłaszcza licencji oprogramowania! I ich wersji bezpłatnej jest pełna ograniczeń) lub wolne alternatywy, takie jak PostgreSQL (nie mogę wiele powiedzieć o tym, z powodu braku doświadczenie) lub MySQL (nie że silny w porównaniu do Oracle, na przykład nie ma natywny/klarowny roztwór do dynamicznego wychylenia które najprawdopodobniej będą chcieli wykorzystać, itp)

odpowiedź OLD:

ty może to zrobić w ten sposób (proszę znaleźć objaśnienia jako komentarze w kodzie):

# 
# <setup> 
# 
dates1 = pd.date_range('2016-03-15','2016-04-15') 
dates2 = ['2016-02-01', '2016-05-01', '2016-04-01', '2015-01-01', '2016-03-20'] 
dates2 = [pd.to_datetime(d) for d in dates2] 

countries = ['c1', 'c2', 'c3'] 

t1 = pd.DataFrame({ 
    'date': dates1, 
    'country': np.random.choice(countries, len(dates1)), 
    'cluster': np.random.randint(1, 4, len(dates1)), 
    'count': np.random.randint(1, 10, len(dates1)) 
}) 
t2 = pd.DataFrame({'date': np.random.choice(dates2, 10), 'country': np.random.choice(countries, 10)}) 
# 
# </setup> 
# 

# merge two DFs by `country` 
merged = pd.merge(t1.rename(columns={'date':'date1'}), t2, on='country') 

# filter dates and drop 'date1' column 
merged = merged[(merged.date <= merged.date1 + pd.Timedelta('30days'))\ 
       & \ 
       (merged.date >= merged.date1) 
       ].drop(['date1'], axis=1) 

# group `merged` DF by ['country', 'date', 'cluster'], 
# sum up `counts` for overlapping dates, 
# reset the index, 
# pivot: convert `cluster` values to columns, 
#  taking sum's of `count` as values, 
#  NaN's will be replaced with zeroes 
# and finally reset the index 
r = merged.groupby(['country', 'date', 'cluster'])\ 
      .sum()\ 
      .reset_index()\ 
      .pivot_table(index=['country','date'], 
         columns='cluster', 
         values='count', 
         aggfunc='sum', 
         fill_value=0)\ 
      .reset_index() 

# rename numeric columns to: 'cluster_N' 
rename_cluster_cols = {x: 'cluster_{0}'.format(x) for x in t1.cluster.unique()} 
r = r.rename(columns=rename_cluster_cols) 

wyjścia (dla moich zbiorów danych):

In [124]: r 
Out[124]: 
cluster country  date cluster_1 cluster_2 cluster_3 
0   c1 2016-04-01   8   0   11 
1   c2 2016-04-01   0   34   22 
2   c3 2016-05-01   4   18   36 
+0

Dzięki maxU, ale jest to inne niż mega scalanie. Moje rzeczywiste dane nie pasują do pamięci w ten sposób. Przypuszczam, że mógłbym iterować przez jego partie ... –

+1

Jeśli twoje dane nie mieszczą się w pamięci, możesz użyć 'dask.dataframe'. Replikuje składnię pandy, ale zapewnia algorytmy porcjowane do obsługi właśnie tego przypadku. Pod maską nadal używa pand, ale obsługuje dla Ciebie chunking/execution/merging. Jest to nowsza biblioteka, ale już dość funkcjonalna w dziedzinie "średnich danych" (tj. Danych zbyt dużych, aby zmieścić się w pamięci, ale nie wymaga jeszcze klastra Hadoop). – jkitchen