2015-08-06 7 views
14

Mam Pandas DataFrame, który wygląda podobnie do tego, ale z 10.000 wierszy i 500 kolumn.Pandas DataFrame: Jak natywnie uzyskać minimum w zakresie wierszy i kolumn

My Dataframe

dla każdego wiersza, chciałbym znaleźć minimalną wartość pomiędzy 3 dni temu na 15:00, a dzisiaj o godzinie 13:30.

Czy jest jakiś rodzimy numpy sposób to zrobić szybko? Moim celem jest uzyskanie minimalnej wartości dla każdego wiersza, mówiąc coś w stylu "jaka jest wartość minimalna od 3 dni temu 15:00 do 0 dni temu (dzisiaj) 13:30?"

W tym konkretnym przykładzie odpowiedzi dla dwóch ostatnich rzędów byłoby:

2011-01-09 2481.22 
2011-01-10 2481.22 

Mój obecny sposób jest taki:

1. Get the earliest row (only the values after the start time) 
2. Get the middle rows 
3. Get the last row (only the values before the end time) 
4. Concat (1), (2), and (3) 
5. Get the minimum of (4) 

Ale to trwa bardzo długo na dużej DataFrame

Następujący kod wygeneruje podobny DF:

import numpy 
import pandas 
import datetime 

numpy.random.seed(0) 

random_numbers = (numpy.random.rand(10, 8)*100 + 2000) 
columns  = [datetime.time(13,0) , datetime.time(13,30), datetime.time(14,0), datetime.time(14,30) , datetime.time(15,0), datetime.time(15,30) ,datetime.time(16,0), datetime.time(16,30)] 
index   = pandas.date_range('2011/1/1', '2011/1/10') 
df    = pandas.DataFrame(data = random_numbers, columns=columns, index = index).astype(int) 

print df 

Oto wersja json z dataframe:

„{ "13:00:00": { "1293840000000": 2085, "1293926400000": 2062, "1294012800000": 2035 "1294099200000": 2086, "1294185600000": 2006, "1294272000000": 2097, "1294358400000": 2078, "1294444800000": 2055, "1294531200000": 2023, "1294617600000": 2024}, "13:30:00 ": {" 1293840000000 ": 2045," 1293926400000 ": 2039," 1294012800000 ": 2035," 1294099200000 ": 2045," 1294185600000 ": 2025," 1294272000000 ": 2099," 1294358400000 ": 2028," 1294444800000 ": 2028 "1294531200000": 2034, "1294617600000": 2010}, "14:00:00": {"1293840000000": 2095, "1293926400000": 2006, "1294012800000": 2001, "1294099200000": 2032, "1294185600000" : 2022, "12 94272000000 ": 2040," 1294358400000 ": 2024," 1294444800000 ": 2070," 1294531200000 ": 2081," 1294617600000 ": 2095}," 14:30:00 ": {" 1293840000000 ": 2057," 1293926400000 ": 2042 "1294012800000": 2018, "1294099200000": 2023, "1294185600000": 2025, "1294272000000": 2016, "1294358400000": 2066, "1294444800000": 2041, "1294531200000": 2098, "1294617600000": 2023}, "15:00:00": {"1293840000000": 2082, "1293926400000": 2025, "1294012800000": 2040, "1294099200000": 2061, "1294185600000": 2013, "1294272000000": 2063, "1294358400000": 2024 "1294444800000": 2036, "1294531200000": 2096, "1294617600000": 2068}, "15:30:00": {"1293840000000": 2090, "1293926400000": 2084, "1294012800000": 2092, "1294099200000" : 2003, "1294185600000": 2001, "1294272000000": 2049, "1294358400000": 2066, "1294444800000": 2082, "1294531200000": 2090, "1294617600000": 2005}, "16:00:00": {" 1293840000000 ": 2081," 1293926400000 ": 2003," 1294012800000 ": 2009," 1294099200000 ": 2001," 1294185600000 ": 2011," 1294272000000 ": 2098," 1294358400000 ": 2051," 1294444800000 ": 2092," 1294531200000 " : 2029, "1294617600000": 2073}, "16: 3 0:00 ": {" 1293840000000 ": 2015," 1293926400000 ": 2095," 1294012800000 ": 2094," 1294099200000 ": 2042," 1294185600000 ": 2061," 1294272000000 ": 2006," 1294358400000 ": 2042," 1294444800000 ": 2004," 1294531200000 ": 2099," 1294617600000 ": 2088}} '

+0

najpierw robi '' rolling_min'' uzyskać minimum dla każdej kolumny w ciągu ostatnich 3 wierszy, a następnie 'min' uzyskać minimum, że nowy wiersze, czego potrzebujesz? – joris

+0

Skąd pochodzisz: "2011-01-10 2481.22"? Czy możesz wyjaśnić swoje oczekiwane wyniki w nieco bardziej szczegółowy sposób? – Divakar

+0

Pewnie. Weź rząd 2011-01-10. Chcę zebrać wszystkie wartości sprzed 3 dni (3 wiersze temu) po godzinie 15:00 (2011-01-07 15:30 wartość, 2011-01-07 2011-01-07 16:00 wartość, 2011-01 -07 16:30 wartość) do dziś (2011-01-10) 13:30. Więc w zasadzie każda komórka między 2011-01-07 15:30 a dzisiaj 13:30. Po zebraniu tych wartości otrzymuję minimalną wartość pęczka. – user1367204

Odpowiedz

9

Można najpierw układać DataFrame do stworzenia serii, a następnie pokroić w indeksu zgodnie z wymogami i podejmują min. Np

first, last = ('2011-01-07', datetime.time(15)), ('2011-01-10', datetime.time(13, 30)) 
df.stack().loc[first: last].min() 

Wynikiem df.stack jest Series z MultiIndex gdzie poziom wewnętrzny składa się z pierwotnych kolumn. Następnie wycinamy przy użyciu par tuple z datą początkową i końcową oraz czasami. Jeśli zamierzasz wykonywać wiele takich operacji, powinieneś rozważyć przypisanie df.stack() do pewnej zmiennej. Możesz wtedy rozważyć zmianę indeksu na właściwą DatetimeIndex. Następnie możesz pracować z szeregami czasowymi i formatem siatki, zgodnie z wymaganiami.

Oto kolejna metoda, która unika układania w stosy i jest dużo szybsza w DataFrame o rozmiarze, z którym faktycznie pracujesz (jako jednorazowy; cięcie stosu DataFrame jest o wiele szybsze, gdy jest ułożone, więc jeśli robisz wiele z tych operacji należy ułożyć i skonwertować indeks).
To mniej ogólne, ponieważ działa z min i max, ale nie z, powiedzmy, mean. Otrzymuje on min podzestawu pierwszego i ostatniego wiersza oraz min wierszy pomiędzy (jeśli występuje) i pobiera min tych trzech kandydatów.

first_row = df.index.get_loc(first[0]) 
last_row = df.index.get_loc(last[0]) 
if first_row == last_row: 
    result = df.loc[first[0], first[1]: last[1]].min() 
elif first_row < last_row: 
    first_row_min = df.loc[first[0], first[1]:].min() 
    last_row_min = df.loc[last[0], :last[1]].min() 
    middle_min = df.iloc[first_row + 1:last_row].min().min() 
    result = min(first_row_min, last_row_min, middle_min) 
else: 
    raise ValueError('first row must be <= last row') 

Zauważ, że jeśli first_row + 1 == last_row następnie middle_min jest nan ale wynik jest nadal aktualna dopóki middle_min nie przychodzi pierwszy w zaproszeniu do min.

6

Weź następujący przykład, łatwiej jest go zrozumieć.

|   | 13:00:00 | 13:30:00 | 14:00:00 | 14:30:00 | 15:00:00 | 15:30:00 | 16:00:00 | 16:30:00 | 
|------------|----------|----------|----------|----------|----------|----------|----------|----------| 
| 2011-01-01 | 2054  | 2071  | 2060  | 2054  | 2042  | 2064  | 2043  | 2089  | 
| 2011-01-02 | 2096  | 2038  | 2079  | 2052  | 2056  | 2092  | 2007  | 2008  | 
| 2011-01-03 | 2002  | 2083  | 2077  | 2087  | 2097  | 2079  | 2046  | 2078  | 
| 2011-01-04 | 2011  | 2063  | 2014  | 2094  | 2052  | 2041  | 2026  | 2077  | 
| 2011-01-05 | 2045  | 2056  | 2001  | 2061  | 2061  | 2061  | 2094  | 2068  | 
| 2011-01-06 | 2035  | 2043  | 2069  | 2006  | 2066  | 2067  | 2021  | 2012  | 
| 2011-01-07 | 2031  | 2036  | 2057  | 2043  | 2098  | 2010  | 2020  | 2016  | 
| 2011-01-08 | 2065  | 2025  | 2046  | 2024  | 2015  | 2011  | 2065  | 2013  | 
| 2011-01-09 | 2019  | 2036  | 2082  | 2009  | 2083  | 2009  | 2097  | 2046  | 
| 2011-01-10 | 2097  | 2060  | 2073  | 2003  | 2028  | 2012  | 2029  | 2011  | 

powiedzmy chcemy znaleźć min z (2, b) do (d), 6, dla każdego rzędu.

Możemy po prostu wypełnić niepożądane dane z pierwszego i ostatniego wiersza przez np.inf.

df.loc["2011-01-07", :datetime.time(15, 0)] = np.inf 
df.loc["2011-01-10", datetime.time(13, 30):] = np.inf 

masz

|   | 13:00:00 | 13:30:00 | 14:00:00 | 14:30:00 | 15:00:00 | 15:30:00 | 16:00:00 | 16:30:00 | 
|------------|----------|----------|----------|----------|----------|----------|----------|----------| 
| 2011-01-01 | 2054.0 | 2071.0 | 2060.0 | 2054.0 | 2042.0 | 2064.0 | 2043.0 | 2089.0 | 
| 2011-01-02 | 2096.0 | 2038.0 | 2079.0 | 2052.0 | 2056.0 | 2092.0 | 2007.0 | 2008.0 | 
| 2011-01-03 | 2002.0 | 2083.0 | 2077.0 | 2087.0 | 2097.0 | 2079.0 | 2046.0 | 2078.0 | 
| 2011-01-04 | 2011.0 | 2063.0 | 2014.0 | 2094.0 | 2052.0 | 2041.0 | 2026.0 | 2077.0 | 
| 2011-01-05 | 2045.0 | 2056.0 | 2001.0 | 2061.0 | 2061.0 | 2061.0 | 2094.0 | 2068.0 | 
| 2011-01-06 | 2035.0 | 2043.0 | 2069.0 | 2006.0 | 2066.0 | 2067.0 | 2021.0 | 2012.0 | 
| 2011-01-07 | inf  | inf  | inf  | inf  | inf  | 2010.0 | 2020.0 | 2016.0 | 
| 2011-01-08 | 2065.0 | 2025.0 | 2046.0 | 2024.0 | 2015.0 | 2011.0 | 2065.0 | 2013.0 | 
| 2011-01-09 | 2019.0 | 2036.0 | 2082.0 | 2009.0 | 2083.0 | 2009.0 | 2097.0 | 2046.0 | 
| 2011-01-10 | 2097.0 | inf  | inf  | inf  | inf  | inf  | inf  | inf  | 

Aby uzyskać wynik:

df.loc["2011-01-07": "2011-01-10", :].idxmin(axis=1) 

2011-01-07 15:30:00 
2011-01-08 15:30:00 
2011-01-09 14:30:00 
2011-01-10 13:00:00 
Freq: D, dtype: object 
+0

Czy jest jakiś sposób zrobienia wektoryzacji dla każdego wiersza w ramce danych? – user1367204

+0

Co masz na myśli przez wektoryzację? używając funkcji budowania pand? –

+0

Więc mogę powiedzieć, powiedz, uzyskać minimalną wartość dla każdego wiersza od 3 dni temu 15:00 do 0 dni temu 11:30 – user1367204

6

hacky sposób, ale powinna być szybka, jest Concat z przesuniętych DataFrames:

In [11]: df.shift(1) 
Out[11]: 
      13:00:00 13:30:00 14:00:00 14:30:00 15:00:00 15:30:00 16:00:00 16:30:00 
2011-01-01  NaN  NaN  NaN  NaN  NaN  NaN  NaN  NaN 
2011-01-02  2054  2071  2060  2054  2042  2064  2043  2089 
2011-01-03  2096  2038  2079  2052  2056  2092  2007  2008 
2011-01-04  2002  2083  2077  2087  2097  2079  2046  2078 
2011-01-05  2011  2063  2014  2094  2052  2041  2026  2077 
2011-01-06  2045  2056  2001  2061  2061  2061  2094  2068 
2011-01-07  2035  2043  2069  2006  2066  2067  2021  2012 
2011-01-08  2031  2036  2057  2043  2098  2010  2020  2016 
2011-01-09  2065  2025  2046  2024  2015  2011  2065  2013 
2011-01-10  2019  2036  2082  2009  2083  2009  2097  2046 

In [12]: df.shift(2).iloc[:, 4:] 
Out[12]: 
      15:00:00 15:30:00 16:00:00 16:30:00 
2011-01-01  NaN  NaN  NaN  NaN 
2011-01-02  NaN  NaN  NaN  NaN 
2011-01-03  2042  2064  2043  2089 
2011-01-04  2056  2092  2007  2008 
2011-01-05  2097  2079  2046  2078 
2011-01-06  2052  2041  2026  2077 
2011-01-07  2061  2061  2094  2068 
2011-01-08  2066  2067  2021  2012 
2011-01-09  2098  2010  2020  2016 
2011-01-10  2015  2011  2065  2013 

In [13]: pd.concat([df.iloc[:, :1], df.shift(1), df.shift(2).iloc[:, 4:]], axis=1) 
Out[13]: 
      13:00:00 13:00:00 13:30:00 14:00:00 14:30:00 15:00:00 15:30:00 16:00:00 16:30:00 15:00:00 15:30:00 16:00:00 16:30:00 
2011-01-01  2054  NaN  NaN  NaN  NaN  NaN  NaN  NaN  NaN  NaN  NaN  NaN  NaN 
2011-01-02  2096  2054  2071  2060  2054  2042  2064  2043  2089  NaN  NaN  NaN  NaN 
2011-01-03  2002  2096  2038  2079  2052  2056  2092  2007  2008  2042  2064  2043  2089 
2011-01-04  2011  2002  2083  2077  2087  2097  2079  2046  2078  2056  2092  2007  2008 
2011-01-05  2045  2011  2063  2014  2094  2052  2041  2026  2077  2097  2079  2046  2078 
2011-01-06  2035  2045  2056  2001  2061  2061  2061  2094  2068  2052  2041  2026  2077 
2011-01-07  2031  2035  2043  2069  2006  2066  2067  2021  2012  2061  2061  2094  2068 
2011-01-08  2065  2031  2036  2057  2043  2098  2010  2020  2016  2066  2067  2021  2012 
2011-01-09  2019  2065  2025  2046  2024  2015  2011  2065  2013  2098  2010  2020  2016 
2011-01-10  2097  2019  2036  2082  2009  2083  2009  2097  2046  2015  2011  2065  2013 

i wziąć minimum całej kolumny (upewniając się odrzucić kolumn, które są zbyt wcześnie lub zbyt późno w dany dzień:

In [14]: pd.concat([df.iloc[:, :1], df.shift(1), df.shift(2).iloc[:, 4:]], axis=1).min(1) 
Out[14]: 
2011-01-01 2054 
2011-01-02 2042 
2011-01-03 2002 
2011-01-04 2002 
2011-01-05 2011 
2011-01-06 2001 
2011-01-07 2006 
2011-01-08 2010 
2011-01-09 2010 
2011-01-10 2009 
Freq: D, dtype: float64 

można to zrobić bardziej efektywnie, ale bardziej hałaśliwie, biorąc minimum każdy przesunięty DataFrame przed concatting:

In [21]: pd.concat([df.iloc[:, :1].min(1), 
        df.shift(1).min(1), 
        df.shift(2).iloc[:, 4:].min(1)], 
        axis=1).min(1) 
Out[21]: 
2011-01-01 2054 
2011-01-02 2042 
2011-01-03 2002 
2011-01-04 2002 
2011-01-05 2011 
2011-01-06 2001 
2011-01-07 2006 
2011-01-08 2010 
2011-01-09 2010 
2011-01-10 2009 
Freq: D, dtype: float64 

Albo będzie znacznie szybszy niż zapętlenie dni.

+0

To jest najbliższy temu, co chcę, ale chciałbym, żeby zmiany nie były mocno zakodowane. Czasami chciałbym wziąć minimum 5 rzędów, a czasem tylko 2 rzędy. Czy wiesz, jak to zrobić? – user1367204

+0

@ user1367204 umieść go w funkcji. –

+0

Twoja sugestia jest bardzo podobna do obecnej metody, którą robię, i sposobu, który opisałem jako moje obecne rozwiązanie w opisie problemu. Czy wiesz o rodzimym sposobie niefunkcjonowania w związku z pandami? – user1367204

5

Użyłem metody stackand (pandy) i obiektu timeseries do zbudowania wyniku z przykładowych danych. To podejście uogólnia się dobrze do dowolnego zakresu czasu z kilkoma zmianami i wykorzystuje pandy wbudowane w funkcje do zbudowania wyniku.

import pandas as pd 
import datetime as dt 
# import df from json 
df = pd.read_json('''{"13:00:00":  {"1293840000000":2085,"1293926400000":2062,"1294012800000":2035,"1294099200000":2086,"1294185600000":2006,"1294272000000":2097,"1294358400000":2078,"1294444800000":2055,"1294531200000":2023,"1294617600000":2024}, 
         "13:30:00":{"1293840000000":2045,"1293926400000":2039,"1294012800000":2035,"1294099200000":2045,"1294185600000":2025,"1294272000000":2099,"1294358400000":2028,"1294444800000":2028,"1294531200000":2034,"1294617600000":2010}, 
         "14:00:00":{"1293840000000":2095,"1293926400000":2006,"1294012800000":2001,"1294099200000":2032,"1294185600000":2022,"1294272000000":2040,"1294358400000":2024,"1294444800000":2070,"1294531200000":2081,"1294617600000":2095}, 
         "14:30:00":{"1293840000000":2057,"1293926400000":2042,"1294012800000":2018,"1294099200000":2023,"1294185600000":2025,"1294272000000":2016,"1294358400000":2066,"1294444800000":2041,"1294531200000":2098,"1294617600000":2023}, 
         "15:00:00":{"1293840000000":2082,"1293926400000":2025,"1294012800000":2040,"1294099200000":2061,"1294185600000":2013,"1294272000000":2063,"1294358400000":2024,"1294444800000":2036,"1294531200000":2096,"1294617600000":2068}, 
         "15:30:00":{"1293840000000":2090,"1293926400000":2084,"1294012800000":2092,"1294099200000":2003,"1294185600000":2001,"1294272000000":2049,"1294358400000":2066,"1294444800000":2082,"1294531200000":2090,"1294617600000":2005}, 
         "16:00:00":{"1293840000000":2081,"1293926400000":2003,"1294012800000":2009,"1294099200000":2001,"1294185600000":2011,"1294272000000":2098,"1294358400000":2051,"1294444800000":2092,"1294531200000":2029,"1294617600000":2073}, 
         "16:30:00":{"1293840000000":2015,"1293926400000":2095,"1294012800000":2094,"1294099200000":2042,"1294185600000":2061,"1294272000000":2006,"1294358400000":2042,"1294444800000":2004,"1294531200000":2099,"1294617600000":2088}} 
        '''#,convert_axes=False 
        ) 
date_idx=df.index      
# stack the data 
stacked = df.stack() 
# merge the multindex into a single idx. 
idx_list = stacked.index.tolist() 
idx = [] 
for item in idx_list: 
    day = item[0] 
    time = item[1] 
    idx += [dt.datetime(day.year, day.month, day.day, time.hour, time.minute)] 
# make a time series to simplify slicing 
timeseries = pd.TimeSeries(stacked.values, index=idx) 
# get the results for each date 

for i in range(2, len(date_idx)): 
    # get the min values for each day in the sample data. 
    start_time='%s 15:00:00'%date_idx[i-2] 
    end_time = '%s 13:30:00'%date_idx[i] 
    slice_idx =timeseries.index>=start_time 
    slice_idx *= timeseries.index<=end_time 
    print "%s %s"%(date_idx[i].date(), timeseries[slice_idx].min()) 

wyjściowa:

2011-01-03 2003 
2011-01-04 2001 
2011-01-05 2001 
2011-01-06 2001 
2011-01-07 2001 
2011-01-08 2006 
2011-01-09 2004 
2011-01-10 2004 
Powiązane problemy