Pandas DataFrame: как изначально получить минимальный диапазон строк и столбцов

У меня есть Pandas DataFrame, который похож на этот, но с 10 000 строк и 500 столбцов.

Мой Dataframe

Для каждой строки я хотел бы найти минимальное значение между 3 днями назад в 15:00 и сегодня в 13:30.

Есть ли какой-нибудь собственный способ numpy сделать это быстро? Моя цель - получить минимальное значение для каждой строки, сказав что-то вроде "каково минимальное значение от 3 дней назад назад 15:00 до 0 дней назад (ака сегодня) 13:30?"

Для этого конкретного примера ответы для двух последних строк будут следующими:

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

Мой текущий способ:

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)

Но это занимает очень много времени на большом DataFrame


Следующий код сгенерирует аналогичный 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

Вот 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, "129418 5600000": 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}}р >

Ответы

Ответ 1

Вы можете сначала скомпилировать DataFrame для создания серии, а затем проиндексировать ее по мере необходимости и взять min. Например:

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

Результатом df.stack является Series с a MultiIndex, где внутренний уровень состоит из исходных столбцов. Затем мы нарезаем, используя пары tuple с датой начала и окончания и временем. Если вы собираетесь делать много таких операций, вам следует рассмотреть возможность назначения df.stack() некоторой переменной. Затем вы можете рассмотреть возможность изменения индекса на правильный DatetimeIndex. Затем вы можете работать как с временными рядами, так и с форматом сетки по мере необходимости.

Вот еще один метод, который позволяет избежать стекирования и намного быстрее на DataFrames того размера, с которым вы фактически работаете (как одноразовый, нарезка сложенной DataFrame намного быстрее, если она укладывается, так что если вы выполняя многие из этих операций, вы должны складывать и конвертировать индекс).
Он менее общий, поскольку он работает с min и max, но не с, скажем, mean. Он получает min подмножества первой и последней строк и min строк между (если они есть) и принимает min этих трех кандидатов.

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

Обратите внимание, что если first_row + 1 == last_row, то middle_min есть nan, но результат все равно правильный, если middle_min не приходит первым в вызове min.

Ответ 2

Возьмем следующий пример, это легче понять.

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

Скажем, мы хотим найти min от (2, b) до (6, d) для каждой строки.

Мы можем просто заполнить нежелательные данные первой и последней строки np.inf.

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

вы получаете

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

Чтобы получить результат:

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

Ответ 3

Хакерный способ, но должен быть быстрым, заключается в том, чтобы скомпоновать сдвинутые 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

и возьмите минимум по столбцам (убедитесь, что вы сбросили столбцы, которые слишком рано или слишком поздно в определенный день:

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

Вы можете сделать это более эффективно, но более шумно, взяв минимум каждого сдвинутого DataFrame перед конкатенацией:

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

Либо будет значительно быстрее, чем цикл через дни.

Ответ 4

Я использовал метод pandas 'stack() и объект timeseries для создания результата из данных образца. Этот подход хорошо обобщается на любой произвольный временной диапазон с несколькими настройками и использует pandas встроенные функционалы для построения результата.

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

выход:

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