Ich habe einen Pandas DataFrame, der ähnlich aussieht, aber 10.000 Zeilen und 500 Spalten enthält.

My Dataframe

Für jede Zeile möchte ich den Mindestwert zwischen vor 3 Tagen um 15:00 Uhr und heute um 13:30 Uhr ermitteln.

Gibt es eine native numpy Möglichkeit, dies schnell zu tun? Mein Ziel ist es, den Mindestwert für jede Zeile zu erhalten, indem ich etwas sage wie "Was ist der Mindestwert von vor 3 Tagen vor 15:00 bis 0 Tagen vor (auch bekannt als heute) 13:30?"

Für dieses spezielle Beispiel wären die Antworten für die letzten beiden Zeilen:

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

Mein aktueller Weg ist folgender:

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)

Bei einem großen DataFrame dauert dies jedoch sehr lange


Der folgende Code generiert einen ähnlichen 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

Hier ist die JSON-Version des Datenrahmens:

'{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, 129418560 0000: 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}} '

14
user1367204 7 Aug. 2015 im 01:25

4 Antworten

Beste Antwort

Sie können den DataFrame zuerst stapeln, um eine Serie zu erstellen, ihn dann nach Bedarf indizieren und die min. Beispielsweise:

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

Das Ergebnis von df.stack ist ein Series mit einem MultiIndex, wobei die innere Ebene aus den ursprünglichen Spalten besteht. Wir schneiden dann mit tuple Paaren mit dem Start- und Enddatum und der Startzeit. Wenn Sie viele solcher Operationen ausführen möchten, sollten Sie in Betracht ziehen, einer Variablen df.stack() zuzuweisen. Sie können dann den Index in einen richtigen DatetimeIndex ändern. Anschließend können Sie nach Bedarf sowohl mit der Zeitreihe als auch mit dem Rasterformat arbeiten.

Hier ist eine weitere Methode, die das Stapeln vermeidet und bei DataFrames der Größe, mit der Sie tatsächlich arbeiten, viel schneller ist (einmalig: Das Schneiden des gestapelten DataFrame ist nach dem Stapeln viel schneller, wenn Sie dies tun Bei vielen dieser Operationen sollten Sie den Index stapeln und konvertieren.
Es ist weniger allgemein, da es mit min und max funktioniert, aber nicht beispielsweise mit mean. Es erhält das min der Teilmenge der ersten und letzten Zeile und das min der dazwischen liegenden Zeilen (falls vorhanden) und nimmt das min dieser drei Kandidaten.

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

Beachten Sie, dass wenn first_row + 1 == last_row, dann middle_min nan ist, das Ergebnis aber immer noch korrekt ist, solange middle_min beim Aufruf von min nicht an erster Stelle steht.

9
JoeCondron 1 Okt. 2015 im 10:32

Ich habe die stack () -Methode und das timeseries-Objekt von pandas verwendet, um das Ergebnis aus den Beispieldaten zu erstellen. Dieser Ansatz lässt sich mit wenigen Anpassungen gut auf jeden beliebigen Zeitbereich verallgemeinern und verwendet Pandas, die in Funktionalitäten integriert sind, um das Ergebnis zu erstellen.

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

Ausgabe:

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
5
PabTorre 26 Sept. 2015 im 06:52

Ein hackiger Weg, der aber schnell sein sollte, besteht darin, die verschobenen DataFrames zu konzentrieren:

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

Und nehmen Sie das Minimum über die Spalten (stellen Sie sicher, dass Sie die Spalten verwerfen, die an einem bestimmten Tag zu früh oder zu spät sind:

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

Sie können dies effizienter, aber geräuschvoller tun, indem Sie das Minimum jedes verschobenen DataFrame vor dem Konatieren nehmen:

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

Beides ist erheblich schneller als das Durchlaufen von Tagen.

6
Andy Hayden 24 Sept. 2015 im 20:58

Nehmen Sie das folgende Beispiel, es ist leichter zu verstehen.

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

Angenommen, wir möchten die min von (2, b) bis (6, d) für jede Zeile ermitteln.

Wir können einfach die unerwünschten Daten der ersten und der letzten Zeile mit np.inf füllen.

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

Du erhältst

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

Um das Ergebnis zu erhalten:

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
6
Jia Li 24 Sept. 2015 im 15:41