Лучшее применение apply для вычисления новых значений с использованием фактической итерированной строки и строк одного и того же целого столбца - PullRequest
3 голосов
/ 16 апреля 2020

На основании данных этого примера:

data = """value          
"2020-03-02"    2
"2020-03-03"    4
"2020-03-01"    3
"2020-03-04"    0
"2020-03-08"    0
"2020-03-06"    0
"2020-03-07"    2"""
  • Я заказываю value по дате в качестве индекса даты и времени
  • из столбца value Я вычисляю новый cum_value столбец кумулятивного значения;
  • для каждого значения строки vc{i from 0 to n} из value_cum,
  • Я ищу в vc'{j from 0 to i} вырезанной серии cum_value строку, которая проверяет и максимизирует соотношение vc{i} / vc'{j} >= 2

В конце я получаю для каждого дня дельту между фактическим днем ​​и днем, которые максимизируют предикат. Для этих данных я получаю:

            value  value_cum  computeValue  delta
2020-03-01      3          3           NaN    NaN
2020-03-02      2          5           NaN    NaN
2020-03-03      4          9           3.0    2.0
2020-03-04      0          9           3.0    2.0
2020-03-06      0          9           3.0    2.0
2020-03-07      2         11           2.2    5.0
2020-03-08      0         11           2.2    5.0

Редактировать: Больше контекстной информации здесь

На самом деле это код для определения первого дня удвоения для числа Covid19 Накопленная смерть. :

  • value - моя смерть за день,
  • value_cum - накопленная смерть за день.

Для каждого дня я ищу в существующий ряд, когда отношение накопленных смертей умножается на 2. Вот почему я сокращаю ряды, чтобы вычислить мой коэффициент, мне нужно только n предыдущих дат / строк (прошедшего дня) до фактического дня, который я хочу проверить.

Я нашел это вычисление на COVID 19 в нашем мире в данных диаграмм, но я хочу вычислить эти показатели для одной страны и для каждого дня, а не только для последнего дня, как показано на рисунке :)

enter image description here

Например, для даты 2020-03-04 мне нужно только вычислить соотношение между 2020-03-04 и 2020-03- 01/02/03, чтобы найти ПЕРВУЮ дату, когда отношение> = 2

В этом примере 2020-03-04 не больше смертей, чем 2020-03-03, поэтому мы не хотим вычислять новая дельта (количество дней до смерти, умноженное> = 2, равно 2020-03-03!). Я объясняю это в Edit1 / 2 в архиве в конце этого поста.

Мы используем словарь для хранения первого вхождения каждого накопленного значения, поэтому, когда я вижу, что cum_value = значение, я выполняю поиск в словаре, чтобы получить правильную дату (возврат 9 2020-20-03) для отношения вычисление.

Вот мой фактический рабочий код для этого:

    import pandas as pd
    import io
    from dfply import *

data = """value          
"2020-03-02"    2
"2020-03-03"    4
"2020-03-01"    3
"2020-03-04"    0
"2020-03-08"    0
"2020-03-06"    0
"2020-03-07"    2"""

   df = pd.read_table(io.StringIO(data), delim_whitespace=True)
df.index = pd.to_datetime(df.index)

def f(x, **kwargs):

    # get numerical index of row
    numericIndex = kwargs["df"].index.get_loc(x.name)
    dict_inverted = kwargs["dict"]

    # Skip the first line, returning Nan
    if numericIndex == 0:
        return np.NaN, np.NaN


    # If value_cum is the same than the previous row (nothing changed),
    # we need some tweaking (compute using the datebefore) to return same data
    ilocvalue = kwargs["df"].iloc[[numericIndex - 1]]["value_cum"][0]
    if x['value_cum'] == ilocvalue:
        name = dict_inverted[x['value_cum']]
    else:
        name = x.name

    # Series to compare with actual row
    series =  kwargs["value_cum"]
    # Cut this series by taking in account only the days before actual date
    cutedSeries = series[series.index < name]
    rowValueToCompare = float(x['value_cum'])

    # User query to filter rows
    # https://stackoverflow.com/questions/40171498/is-there-a-query-method-or-similar-for-pandas-series-pandas-series-query
    result = cutedSeries.to_frame().query(f'({rowValueToCompare} / value_cum) >= 2.0')

    # If empty return Nan
    if result.empty:
        return np.NaN, np.NaN 

    # Get the last result
    oneResult = result.tail(1).iloc[:, 0]
    # Compute values to return
    value = (rowValueToCompare/oneResult.values[0])
    idx = oneResult.index[0]
    # Delta between the actual row day, and the >=2 day
    delta = name - idx

    # return columns
    return value, delta.days

df_cases = df >> arrange(X.index, ascending=True) \
        >> mutate(value_cum=cumsum(X.value))


df_map_value = df_cases.drop_duplicates(["value_cum"])
dict_value = df_map_value["value_cum"].to_dict()
dict_value_inverted = {v: k for k, v in dict_value.items()}
print(dict_value_inverted)

df_cases[["computeValue", "delta"]] = df_cases.apply(f, result_type="expand", dict=dict_value_inverted, df=df_cases, value_cum= df_cases['value_cum'],axis=1)
print(df_cases)

Я не очень доволен этим кодом, я обнаружил, что передача всего DF в мой метод apply была странной.

Я уверен, что в Panda есть какой-то лучший код, который делает это в несколько строк и более элегантно, используя, вероятно, вложенный метод apply, но я не нашел как.

Словарь метод хранения даты первого дубликата также странный, я не знаю, возможно ли это сделать с помощью apply (повторное использование результата предыдущих вычислений во время применения) или единственный способ - написать рекурсивную функцию.

ВОПРОС ОБНОВЛЕНО РЕДАКТИРОВАТЬ 1/2/3, РАБОТАЕТ С ДВОЙНЫМИ ЗНАЧЕНИЯМИ

РЕДАКТИРОВАТЬ В АРХИВЕ

Редактировать 1:

data = """value          
"2020-03-02"    1
"2020-03-03"    0
"2020-03-01"    1
"2020-03-04"    0
"2020-03-05"    4"""

Я вижу, что мой код не учитывается, когда значение равно нулю.

                value  value_cum  computeValue  delta
2020-03-01      1          1           NaN    NaN
2020-03-02      1          2           2.0    1.0
2020-03-03      0          2           2.0    2.0
2020-03-04      0          2           2.0    3.0
2020-03-05      4          6           3.0    1.0

2020-03-03 computeValue равно 3,0, а не 2,0, dela равно 2,0 дням, а не 1,0 дням (как 2020-03-02)

Я не могу получить доступ предыдущие значения во время применения вычислений, поэтому я ищу другой способ сделать это.

Edit 2:

Найден способ передачи предварительно вычисленного словаря:

  • удаление дубликата
  • словарь, в котором value_cum возвращает метку времени
   df_map_value = df_cases.drop_duplicates(["value_cum"])
   dict_value = df_map_value["value_cum"].to_dict()
   dict_value_inverted = {v: k for k, v in dict_value.items()}
   print(dict_value_inverted)

Теперь, когда я нашел значение cum_value, равное некоторому значению, я возвращаю индекс, используемый для вычислений .

Ответы [ 3 ]

6 голосов
/ 19 апреля 2020

Некоторые баллы

Пример, который вы привели, немного прост, и я считаю, что в более обобщенном c случае немного сложнее думать. Затем я сгенерировал случайные данные за 30 дней, используя numpy.

. Просматривая отправленную вами ссылку, я думаю, что они показывают нам, «сколько дней - это последний день, который в текущем дне вдвое больше, чем current_day» ".

Чтобы показать это в явном виде, я буду использовать очень подробные имена столбцов в pandas, и прежде чем вычислять нужные метрики, я построю в фрейме данных список ссылок с именем days_current_day_is_double_of, который будет для каждой строки ( день) подсчитать список дней, в котором текущий deaths_cum является удвоенным числом за день deaths_cum.

Этот столбец позже можно заменить простой операцией np.where () каждый раз, когда вы хотите найти это для строки, если вы не хотите сохранять список ссылок в кадре данных. Я думаю, что это понятнее -

генерация данных

import pandas as pd
import numpy as np
import io
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

#n_of_days = 30
#random_data = np.random.randint(0,100,size=n_of_days)
#date_range = pd.date_range(start="2020-03-02",freq="D",periods=n_of_days)
#random_data = pd.DataFrame({"deaths":random_data})
#random_data.index = pd.to_datetime(date_range)
#df= random_data

import requests
import json
response = requests.get("https://api-covid.unthinkingdepths.fr/covid19/ecdc?type=cum")
data = json.loads(response.text)["data"]
deaths_cums = [x["deaths_cum"] for x in data]
dates = [x["dateRep"] for x in data]
df = pd.DataFrame({"deaths_cum":deaths_cums})
df.index = pd.to_datetime(dates)

Подробное решение в pandas

Ключ здесь:

  1. с использованием apply (axis = 1) для итерации по строкам ,
  2. с использованием apply () для перебора столбцов

  3. use np.where явный поиск в обратном направлении Я использую np.where внутри вспомогательной функции check_condition(row), чтобы создать ссылки на дни, а затем использую find_index(list_of_days, idx) для повторного поиска в любое время

  4. * 1056. * лямбда-функции, но организуйте их с помощью «вспомогательных функций»

большая картинка кода

    # create helper functions
    def check_condition(row):
+---  7 lines: ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    def delta_fromlast_day_currDay_is_double_of(row):
+--- 12 lines: ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    def how_many_days_fromlast_day_currDay_is_double_of(row):
+--- 11 lines: ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    def find_index(list_of_days,index):
+---  4 lines: {-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    # use apply here with lambda functions
+--- 23 lines: df['deaths_cum'] = np.cumsum(df['deaths'])------------------------------------------------------------------------------------------------------------------------------------------------
    print(df)

Полный код решения

def check_condition(row):
    row_idx = df.index.get_loc(row.name)
    currRow_deaths_cum = df.iloc[row_idx]['deaths_cum']
    rows_before_current_deaths_cum = df.iloc[:row_idx]['deaths_cum']
    currRow_is_more_thanDobuleOf = np.where((currRow_deaths_cum/rows_before_current_deaths_cum) >= 2)[0]
    return currRow_is_more_thanDobuleOf

def delta_fromlast_day_currDay_is_double_of(row):
    row_idx = df.index.get_loc(row.name)
    currRow_deaths_cum = df.iloc[row_idx]['deaths_cum']
    list_of_days = df.iloc[row_idx]['days_current_day_is_double_of']
    last_day_currDay_is_double_of = find_index(list_of_days,-1)
    if last_day_currDay_is_double_of is np.nan:
        delta = np.nan
    else:
        last_day_currDay_is_double_of_deaths_cum = df.iloc[last_day_currDay_is_double_of]["deaths_cum"]
        delta = currRow_deaths_cum - last_day_currDay_is_double_of_deaths_cum
    return delta

def how_many_days_fromlast_day_currDay_is_double_of(row):
    row_idx = df.index.get_loc(row.name)
    list_of_days = df.iloc[row_idx]['days_current_day_is_double_of']
    last_day_currDay_is_double_of = find_index(list_of_days,-1)
    if last_day_currDay_is_double_of is np.nan:
        delta = np.nan
    else:
        delta = row_idx - last_day_currDay_is_double_of
    return delta

def find_index(list_of_days,index):
    if list_of_days.any(): return list_of_days[index]
    else: return np.nan

# use apply here with lambda functions
#df['deaths_cum'] = np.cumsum(df['deaths'])
df['deaths_cum_ratio_from_day0'] = df['deaths_cum'].apply(
                                lambda cum_deaths: cum_deaths/df['deaths_cum'].iloc[0]
                                                   if df['deaths_cum'].iloc[0] != 0
                                                   else np.nan
                                )
#df['increase_in_deaths_cum'] = df['deaths_cum'].diff().cumsum() <- this mmight be interesting for you to use for other analyses
df['days_current_day_is_double_of'] = df.apply(
                                        lambda row:check_condition(row),
                                        axis=1
                                            )
df['first_day_currDay_is_double_of'] = df['days_current_day_is_double_of'].apply(lambda list_of_days: find_index(list_of_days,0))
df['last_day_currDay_is_double_of'] = df['days_current_day_is_double_of'].apply(lambda list_of_days: find_index(list_of_days,-1))
df['delta_fromfirst_day'] = df['deaths_cum'] - df['deaths_cum'].iloc[0]
df['delta_fromlast_day_currDay_is_double_of'] = df.apply(
                                        lambda row: delta_fromlast_day_currDay_is_double_of(row),
                                        axis=1
                                            )
df['how_many_days_fromlast_day_currDay_is_double_of'] = df.apply(
                                            lambda row: how_many_days_fromlast_day_currDay_is_double_of(row),
                                            axis=1
                                                )
print(df[-30:])

PANDAS ВЫХОД РЕШЕНИЯ

            deaths_cum  deaths_cum_ratio_from_day0  \
2020-03-22         562                         NaN   
2020-03-23         674                         NaN   
2020-03-24         860                         NaN   
2020-03-25        1100                         NaN   
2020-03-26        1331                         NaN   
2020-03-27        1696                         NaN   
2020-03-28        1995                         NaN   
2020-03-29        2314                         NaN   
2020-03-30        2606                         NaN   
2020-03-31        3024                         NaN   
2020-04-01        3523                         NaN   
2020-04-02        4032                         NaN   
2020-04-03        4503                         NaN   
2020-04-04        6507                         NaN   
2020-04-05        7560                         NaN   
2020-04-06        8078                         NaN   
2020-04-07        8911                         NaN   
2020-04-08       10328                         NaN   
2020-04-09       10869                         NaN   
2020-04-10       12210                         NaN   
2020-04-11       13197                         NaN   
2020-04-12       13832                         NaN   
2020-04-13       14393                         NaN   
2020-04-14       14967                         NaN   
2020-04-15       15729                         NaN   
2020-04-16       17167                         NaN   
2020-04-17       17920                         NaN   
2020-04-18       18681                         NaN   
2020-04-19       19323                         NaN   
2020-04-20       19718                         NaN   

                                days_current_day_is_double_of  \
2020-03-22  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-23  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-24  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-25  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-26  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-27  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-28  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-29  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-30  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-03-31  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-01  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-02  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-03  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-04  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-05  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-06  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-07  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-08  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-09  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-10  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-11  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-12  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-13  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-14  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-15  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-16  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-17  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-18  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-19  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   
2020-04-20  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,...   

            first_day_currDay_is_double_of  last_day_currDay_is_double_of  \
2020-03-22                             0.0                           79.0   
2020-03-23                             0.0                           79.0   
2020-03-24                             0.0                           80.0   
2020-03-25                             0.0                           81.0   
2020-03-26                             0.0                           82.0   
2020-03-27                             0.0                           83.0   
2020-03-28                             0.0                           84.0   
2020-03-29                             0.0                           85.0   
2020-03-30                             0.0                           85.0   
2020-03-31                             0.0                           86.0   
2020-04-01                             0.0                           87.0   
2020-04-02                             0.0                           88.0   
2020-04-03                             0.0                           88.0   
2020-04-04                             0.0                           91.0   
2020-04-05                             0.0                           92.0   
2020-04-06                             0.0                           93.0   
2020-04-07                             0.0                           93.0   
2020-04-08                             0.0                           94.0   
2020-04-09                             0.0                           94.0   
2020-04-10                             0.0                           94.0   
2020-04-11                             0.0                           95.0   
2020-04-12                             0.0                           95.0   
2020-04-13                             0.0                           95.0   
2020-04-14                             0.0                           95.0   
2020-04-15                             0.0                           96.0   
2020-04-16                             0.0                           97.0   
2020-04-17                             0.0                           98.0   
2020-04-18                             0.0                           98.0   
2020-04-19                             0.0                           98.0   
2020-04-20                             0.0                           98.0   

            delta_fromfirst_day  delta_fromlast_day_currDay_is_double_of  \
2020-03-22                  562                                    318.0   
2020-03-23                  674                                    430.0   
2020-03-24                  860                                    488.0   
2020-03-25                 1100                                    650.0   
2020-03-26                 1331                                    769.0   
2020-03-27                 1696                                   1022.0   
2020-03-28                 1995                                   1135.0   
2020-03-29                 2314                                   1214.0   
2020-03-30                 2606                                   1506.0   
2020-03-31                 3024                                   1693.0   
2020-04-01                 3523                                   1827.0   
2020-04-02                 4032                                   2037.0   
2020-04-03                 4503                                   2508.0   
2020-04-04                 6507                                   3483.0   
2020-04-05                 7560                                   4037.0   
2020-04-06                 8078                                   4046.0   
2020-04-07                 8911                                   4879.0   
2020-04-08                10328                                   5825.0   
2020-04-09                10869                                   6366.0   
2020-04-10                12210                                   7707.0   
2020-04-11                13197                                   6690.0   
2020-04-12                13832                                   7325.0   
2020-04-13                14393                                   7886.0   
2020-04-14                14967                                   8460.0   
2020-04-15                15729                                   8169.0   
2020-04-16                17167                                   9089.0   
2020-04-17                17920                                   9009.0   
2020-04-18                18681                                   9770.0   
2020-04-19                19323                                  10412.0   
2020-04-20                19718                                  10807.0   

            how_many_days_fromlast_day_currDay_is_double_of  
2020-03-22                                              3.0  
2020-03-23                                              4.0  
2020-03-24                                              4.0  
2020-03-25                                              4.0  
2020-03-26                                              4.0  
2020-03-27                                              4.0  
2020-03-28                                              4.0  
2020-03-29                                              4.0  
2020-03-30                                              5.0  
2020-03-31                                              5.0  
2020-04-01                                              5.0  
2020-04-02                                              5.0  
2020-04-03                                              6.0  
2020-04-04                                              4.0  
2020-04-05                                              4.0  
2020-04-06                                              4.0  
2020-04-07                                              5.0  
2020-04-08                                              5.0  
2020-04-09                                              6.0  
2020-04-10                                              7.0  
2020-04-11                                              7.0  
2020-04-12                                              8.0  
2020-04-13                                              9.0  
2020-04-14                                             10.0  
2020-04-15                                             10.0  
2020-04-16                                             10.0  
2020-04-17                                             10.0  
2020-04-18                                             11.0  
2020-04-19                                             12.0  
2020-04-20                                             13.0  

Если вы отметили совпадения how_many_days_fromlast_day_currDay_is_double_of точно с XDelta из API:)

Есть так много маленьких предложений на случай Вы хотите действительно обобщить свой код. Я не думаю, что это то, что вы ищете, но я перечислю некоторые из них:

  1. вы можете легко добавить фактор роста в функцию check_growth_condition :
def check_growth_condition(row, growth_factor):
         ....
np.where((currRow_deaths_cum/rows_before_current_deaths_cum) >= growth_factor)[0] # <----- then just change 2 by the growth factor
         ....
Вы можете уменьшить список ссылок на days current day is double of до самой последней даты, в которой текущий день удваивается, потому что все дни до самой последней также будут удваивать отношение. Я оставлю первое и последнее только для того, чтобы показать «диапазон дней».
def check_growth_condition(row, growth_factor):
    ...
    # doing backwards search with np.where
    currRow_is_more_thanDoubleOf = np.where((currRow_deaths_cum/rows_before_current_deaths_cum) >= growth_factor)[0]
    if currRow_is_more_thanDobuleOf.any():
        return np.array([currRow_is_more_thanDobuleOf[0],currRow_is_more_thanDobuleOf[-1]]) # <------ return just first and last
    else:
        return currRow_is_more_thanDobuleOf # empty list
    ...

Обратите внимание, если вы хотите избавиться от ссылки столбец, вам просто нужно использовать np.where((currRow_deaths_cum/rows_before_current_deaths_cum) >= growth_factor)[0] везде, где я использую функцию check_growth_condition. опять np.where всегда выполняет поиск.

если вы хотите обобщить дельты между текущим днем ​​и любым другим днем ​​для любых столбцов, просто передайте day_idx и имя столбца в качестве параметра. Вы могли бы даже обобщить delta_from_any_day вместо того, чтобы просто вычесть, вы передаете функцию в качестве ввода, такую ​​как np.divide для вычисления rat ios или np.subtract для вычисления дельт, как я делаю в примере
def delta_from_any_day(row, day_idx, 
        column_name='deaths_cum',func=np.subtract):
    row_idx = df.index.get_loc(row.name)
    currRow_deaths_cum = df.iloc[row_idx][column_name]
    if day_idx is np.nan:
        delta = np.nan
    else:
        day_idx_deaths_cum = df.iloc[day_idx][column_name]
        delta = func(currRow_deaths_cum, day_idx_deaths_cum)
    return delta

Очиститель Pandas раствор

обратите внимание, что мы просто повторно используем check_growth_condition, find_index для выполнить обратный поиск и delta_from_any_day и вычислить дельты. Мы просто используем эти три во всех других вспомогательных функциях для вычисления вещей.

def check_growth_condition(row, growth_factor):
    row_idx = df.index.get_loc(row.name)
    currRow_deaths_cum = df.iloc[row_idx]['deaths_cum']
    rows_before_current_deaths_cum = df.iloc[:row_idx]['deaths_cum']
    currRow_is_more_thanDoubleOf = np.where((currRow_deaths_cum/rows_before_current_deaths_cum) >= growth_factor)[0]
    if currRow_is_more_thanDoubleOf.any():
        return np.array([currRow_is_more_thanDoubleOf[0], currRow_is_more_thanDoubleOf[-1]])
    else:
        return currRow_is_more_thanDoubleOf # empty list

def find_index(list_of_days,index):
    if list_of_days.any(): return list_of_days[index]
    else: return np.nan

def delta_from_any_day(row, day_idx, column_name='deaths_cum',func=np.subtract):
    row_idx = df.index.get_loc(row.name)
    currRow_deaths_cum = df.iloc[row_idx][column_name]
    if day_idx is np.nan:
        delta = np.nan
    else:
        day_idx_deaths_cum = df.iloc[day_idx][column_name]
        delta = func(currRow_deaths_cum, day_idx_deaths_cum)
    return delta

def delta_fromlast_day_currDay_is_double_of(row):
    row_idx = df.index.get_loc(row.name)
    currRow_deaths_cum = df.iloc[row_idx]['deaths_cum']
    list_of_days = df.iloc[row_idx]['rangeOf_days_current_day_is_double_of']
    last_day_currDay_is_double_of = find_index(list_of_days,-1)
    delta = delta_from_any_day(row, last_day_currDay_is_double_of, column_name="deaths_cum")
    return delta

def how_many_days_fromlast_day_currDay_is_double_of(row):
    row_idx = df.index.get_loc(row.name)
    list_of_days = df.iloc[row_idx]['rangeOf_days_current_day_is_double_of']
    last_day_currDay_is_double_of = find_index(list_of_days,-1)
    delta = delta_from_any_day(row, last_day_currDay_is_double_of, column_name="day_index")
    return delta


# use apply here with lambda functions
#df['deaths_cum'] = np.cumsum(df['deaths'])
#df['deaths_cum_ratio_from_day0'] = df['deaths_cum'].apply(
#                               lambda cum_deaths: cum_deaths/df['deaths_cum'].iloc[0]
#                                                  if df['deaths_cum'].iloc[0] != 0
#                                                  else np.nan
#                               )
#df['increase_in_deaths_cum'] = df['deaths_cum'].diff().cumsum() <- this mmight be interesting for you to use for other analyses
df['rangeOf_days_current_day_is_double_of'] = df.apply(
                                        lambda row:check_growth_condition(row,2),
                                        axis=1
                                            )
df['first_day_currDay_is_double_of'] = df['rangeOf_days_current_day_is_double_of'].apply(lambda list_of_days: find_index(list_of_days,0))
df['last_day_currDay_is_double_of'] = df['rangeOf_days_current_day_is_double_of'].apply(lambda list_of_days: find_index(list_of_days,-1))
df['delta_fromfirst_day'] = df['deaths_cum'] - df['deaths_cum'].iloc[0]
df['delta_fromlast_day_currDay_is_double_of'] = df.apply(
                                        lambda row: delta_fromlast_day_currDay_is_double_of(row),
                                        axis=1
                                            )
df['how_many_days_fromlast_day_currDay_is_double_of'] = df.apply(
                                            lambda row: how_many_days_fromlast_day_currDay_is_double_of(row),
                                            axis=1
                                                )
print(df[-5:])

Чистый вывод

            day_index  deaths_cum rangeOf_days_current_day_is_double_of  \
2020-04-16        107       17167                               [0, 97]   
2020-04-17        108       17920                               [0, 98]   
2020-04-18        109       18681                               [0, 98]   
2020-04-19        110       19323                               [0, 98]   
2020-04-20        111       19718                               [0, 98]   

            first_day_currDay_is_double_of  last_day_currDay_is_double_of  \
2020-04-16                             0.0                           97.0   
2020-04-17                             0.0                           98.0   
2020-04-18                             0.0                           98.0   
2020-04-19                             0.0                           98.0   
2020-04-20                             0.0                           98.0   

            delta_fromfirst_day  delta_fromlast_day_currDay_is_double_of  \
2020-04-16                17167                                   9089.0   
2020-04-17                17920                                   9009.0   
2020-04-18                18681                                   9770.0   
2020-04-19                19323                                  10412.0   
2020-04-20                19718                                  10807.0   

            how_many_days_fromlast_day_currDay_is_double_of  
2020-04-16                                             10.0  
2020-04-17                                             10.0  
2020-04-18                                             11.0  
2020-04-19                                             12.0  
2020-04-20                                             13.0  
1 голос
/ 20 апреля 2020

Это звучит как работа для pd.merge_asof .

def track_growths(df, growth_factor=2):
    df = df.sort_index().reset_index()
    df['index'] = pd.to_datetime(df['index'])
    df['cum_value'] = df['value'].cumsum()

    merged = pd.merge_asof(df.assign(lookup=df['cum_value'] / growth_factor),
                           df.assign(lookup=df['cum_value'].astype(float)),
                           on='lookup',
                           suffixes=['', '_past'])

    result = merged[['index', 'value', 'cum_value']]
    growth = merged['cum_value'] / merged['cum_value_past']
    days_since = (merged['index'] - merged['index_past']).dt.days
    return result.assign(computeValue=growth, delta=days_since).set_index('index')

Это имеет настраиваемый фактор роста, если вы хотите попробовать что-то отличное от 2x.

track_growths(df)
#             value  cum_value  computeValue  delta
# index                                            
# 2020-03-01      3          3           NaN    NaN
# 2020-03-02      2          5           NaN    NaN
# 2020-03-03      4          9           3.0    2.0
# 2020-03-04      0          9           3.0    3.0
# 2020-03-06      0          9           3.0    5.0
# 2020-03-07      2         11           2.2    5.0
# 2020-03-08      0         11           2.2    6.0

track_growths(df, 3)
#             value  cum_value  computeValue  delta
# index                                            
# 2020-03-01      3          3           NaN    NaN
# 2020-03-02      2          5           NaN    NaN
# 2020-03-03      4          9      3.000000    2.0
# 2020-03-04      0          9      3.000000    3.0
# 2020-03-06      0          9      3.000000    5.0
# 2020-03-07      2         11      3.666667    6.0
# 2020-03-08      0         11      3.666667    7.0

track_growths(df, 1.5)
#             value  cum_value  computeValue  delta
# index                                            
# 2020-03-01      3          3           NaN    NaN
# 2020-03-02      2          5      1.666667    1.0
# 2020-03-03      4          9      1.800000    1.0
# 2020-03-04      0          9      1.800000    2.0
# 2020-03-06      0          9      1.800000    4.0
# 2020-03-07      2         11      2.200000    5.0
# 2020-03-08      0         11      2.200000    6.0

Подробное объяснение

Исходя из ваших исходных данных:

df
#             value
# 2020-03-01      3
# 2020-03-02      2
# 2020-03-03      4
# 2020-03-04      0
# 2020-03-06      0
# 2020-03-07      2
# 2020-03-08      0

Сначала убедитесь, что индекс отсортирован, а затем конвертируйте его вернуться к обычному столбцу и разобрать в datetime. Это также хорошее время для добавления совокупного значения, которое поможет нам пройти через существующую подготовку:

df = df.sort_index().reset_index()
df['index'] = pd.to_datetime(df['index'])
df['cum_value'] = df['value'].cumsum()
df
#        index  value  cum_value
# 0 2020-03-01      3          3
# 1 2020-03-02      2          5
# 2 2020-03-03      4          9
# 3 2020-03-04      0          9
# 4 2020-03-06      0          9
# 5 2020-03-07      2         11
# 6 2020-03-08      0         11

Теперь вот большой трюк, в котором merge_asof позволяет искать половинную ставку непосредственно строки:

merged = pd.merge_asof(df.assign(lookup=df['cum_value'] / 2),
                       df.assign(lookup=df['cum_value'].astype(float)),
                       on='lookup',
                       suffixes=['', '_past'])
merged
#        index  value  cum_value  lookup index_past  value_past  cum_value_past
# 0 2020-03-01      3          3     1.5        NaT         NaN             NaN
# 1 2020-03-02      2          5     2.5        NaT         NaN             NaN
# 2 2020-03-03      4          9     4.5 2020-03-01         3.0             3.0
# 3 2020-03-04      0          9     4.5 2020-03-01         3.0             3.0
# 4 2020-03-06      0          9     4.5 2020-03-01         3.0             3.0
# 5 2020-03-07      2         11     5.5 2020-03-02         2.0             5.0
# 6 2020-03-08      0         11     5.5 2020-03-02         2.0             5.0

Это выполнит «обратный» поиск, чтобы попытаться найти соответствие для каждой строки в первом DataFrame. Согласно документам:

При поиске «назад» выбирается последняя строка в правом кадре данных, чья клавиша «on» меньше или равна левой клавише.

Здесь ключом является значение lookup, которое равно половине cum_value для левого (текущего) DataFrame и равно cum_value для правого (исторического) DataFrame. Если мы обновим документы в соответствии с этим случаем, это будет выглядеть примерно так:

Выберите последнюю строку в историческом кадре данных, где cum_value меньше или равно половине текущего cum_value .

Это именно то, что вы хотите: самый последний день в истории с числом случаев не более половины.

Отсюда можно быстро вычислить производное delta и computeValue информация и форматирование результата.

result = merged[['index', 'value', 'cum_value']]
growth = merged['cum_value'] / merged['cum_value_past']
days_since = (merged['index'] - merged['index_past']).dt.days
result.assign(computeValue=growth, delta=days_since).set_index('index')
#             value  cum_value  computeValue  delta
# index                                            
# 2020-03-01      3          3           NaN    NaN
# 2020-03-02      2          5           NaN    NaN
# 2020-03-03      4          9           3.0    2.0
# 2020-03-04      0          9           3.0    3.0
# 2020-03-06      0          9           3.0    5.0
# 2020-03-07      2         11           2.2    5.0
# 2020-03-08      0         11           2.2    6.0
0 голосов
/ 19 апреля 2020

Инициализируйте данные:

import io

data = """value          
"2020-03-02"    2
"2020-03-03"    4
"2020-03-01"    3
"2020-03-04"    0
"2020-03-08"    0
"2020-03-06"    0
"2020-03-07"    2"""

df = pd.read_table(io.StringIO(data), delim_whitespace=True)
df.index = pd.to_datetime(df.index)
df = df.sort_index()

Сначала добавьте совокупный итог df['value'] в виде столбца:

df['value_cum'] = df['value'].cumsum()

Если я правильно вас понимаю, вы смотрите на рост коэффициент этого совокупного итога с момента его создания (то есть его первой записи; .iloc[0]):

day_0 = df['value_cum'].iloc[0]
df['growth_factor_since_day_0'] = df['value_cum'] / day_0

Теперь все, что нам нужно сделать, это проверить, сколько дней понадобилось для того, чтобы оно достигло >=2:

((df['growth_factor_since_day_0'] >= 2) == False).sum()

Вы можете указать порог, как в примере, который вы связали, чтобы предотвратить раннее попадание (например, от значения 1 до 2):

day_0 = df['value_cum'].loc[df['value_cum'] >= 5].min()

Это вернет NaN в столбце df['growth_factor_since_day_0'] в случае, если этот порог еще не достигнут, и мы не получим ложных срабатываний.

...