【问题标题】:Generate the date based on the more than 1 constraint根据超过 1 个约束生成日期
【发布时间】:2019-11-03 14:58:07
【问题描述】:

我有一个数据框 df1,其中包含 date_1 列,其值从 01/09/2019 到 30/09/2019。即 30 个值和相应的计数。

DF1

    date_1    count
    01/09/2019  5
    02/09/2019  4
    03/09/2019  5
    04/09/2019  6
    05/09/2019  7
    06/09/2019  8
    07/09/2019  10
    08/09/2019  9
    09/09/2019  11
    10/09/2019  12
    11/09/2019  13
    12/09/2019  14
    13/09/2019  15
    14/09/2019  16

我想使用带有一些约束的 df1 生成数据帧 df2:

  1. 有一个新列 date_2。

  2. date_2 是根据 df1 中存在的计数特征生成的。

例如:一个新的数据框 df2 将有 5 个条目(因为 count = 5)用于 01/09/2019,并且 date_2 列可以采用 date_1 之前 30 天到 2019 年 8 月 30 日之间的值(date_1 中的当前日期 - 1 ) 即对于 01/09/2019,date_2 可以取的值是从 (01/09/2019 - 30 = 01/08/2019) 到 (01/09/2019 - 1 = 30/08/2019)。

date_2 可以从范围(30 - date_1date_1 - 1)中随机选择,即在我们的示例中为 01/09/2019,从 02/08/2019 到 30/08/2019

需要注意的重要一点是 date_2 在 df2 中的计数也应该增加。

预期输出:

    date_1    count   date_2
    01/09/2019  5     02/08/2019
    01/09/2019  5     10/08/2019
    01/09/2019  5     12/08/2019
    01/09/2019  5     25/08/2019
    01/09/2019  5     28/08/2019
    02/09/2019  4     03/08/2019
    02/09/2019  4     10/08/2019
    02/09/2019  4     20/08/2019
    02/09/2019  4     25/08/2019

编辑

我能够使用该函数生成 date_2 :

def pick_random_delta_in_range(min_days=1, max_days=30):
    if min_days is None and max_days is None:
        return datetime.timedelta(days=1, minutes=0, seconds=0)
    if min_days is None:
        return max_days
    if max_days is None:
        return min_days
    days_to_be_added = random.randint(min_days, max_days)
    return datetime.timedelta(days=days_to_be_added, minutes=0, seconds=0)

def gen_date_by_delta(src_dates, date_format, delta_min, delta_max):
    gen_dates = []
    for dt in src_dates:
        src_date = datetime.datetime.strptime(dt, date_format)

        if src_date is None:
            gen_dates.append("")
            continue

        chosen_delta = pick_random_delta_in_range(min_days=delta_min, max_days=delta_max)

        result_date = (src_date + chosen_delta).strftime(date_format)
        gen_dates.append(result_date)

    return gen_dates

date_2 = gen_date_by_delta(src_dates=df1["date_1"], date_format=date_format, delta_min=1, delta_max=30)

*当前增量是随机生成的,可能会为相同的 date_1 生成相同的增量,从而导致重复条目。我不想生成重复的条目。 *

我也无法理解如何根据计数复制数据框中的字段并相应地生成日期。

任何人都可以帮助/建议一种生成相同的方法。

谢谢

【问题讨论】:

    标签: python pandas numpy


    【解决方案1】:

    定义以下“复制”函数:

    def repl(row):
        d1 = row.date_1
        cnt = row['count']
        dates = [ d1 - pd.Timedelta(n, 'D') for n in
                np.sort(np.random.choice(30, cnt, False))[::-1] ]
        return pd.DataFrame({'date_1': d1, 'count': cnt, 'date_2': dates})
    

    然后应用它,连接结果并保存为DF2

    DF2 = pd.concat(df.apply(repl, axis=1).tolist(), ignore_index=True)
    

    请注意,在上面的代码中 row['count'] 可以被替换 使用 row.count,因为有一个同名的 Pandas 方法。 实际上,这只是 not 如何分配列名的示例。 您应该使用现有方法的名称。

    按照关于“所有值”的评论进行编辑

    要消耗该范围内的所有日期,过程更复杂,并且 涉及创建一个专用类以从池中生成日期。

    日期分配算法如下:

    • 日期池在开始时创建,从 min 日期 - 30 天到 最大日期。
    • 每次通话:
      • 第 1 步:获取第一个可能的日期。
      • 第 2 步:从池中获取更多日期。
      • 在这两个步骤中,分配的日期都会从池中删除。
      • 第 3 步:如果池中没有更多日期,则在允许的范围内, 但我们需要更多,从可能的范围内生成日期,但没有 迄今为止为该行选择的重复日期。

    这个类还包含一个“技巧”,以弥补以下事实: 第一行应用的函数被调用两次。 这是 Pandas 中包含的一项优化,但在这种情况下 它有一个副作用(消耗一些“初始”日期,而不是 实际上包含在结果中),所以我必须对此进行补偿。

    按以下步骤进行:

    创建一对Timedelta变量用于各个点:

    td1 = pd.Timedelta(1, 'D')
    td30 = pd.Timedelta(30, 'D')
    

    然后定义一个日期生成器类:

    class DateGen:
        ''' Dates generator
        d1, d2 - date range
        '''
        def __init__(self, d1, d2):
            rng = pd.date_range(d1, d2, freq='D')
            self.dates = pd.Series(rng, index=rng)
            self.firstCall = True
    
        def popDate(self, d1, d2):
            wrk = self.dates[self.dates.between(d1, d2)]
            siz = wrk.size
            if siz > 0:
                dat = wrk.sample().iloc[0] if siz > 1 else wrk.iloc[0]
                self.dates.pop(dat)
                return dat, True
            return None, False
    
        def popDates(self, d1, d2, n):
            ret = []
            if self.firstCall:
                self.firstCall = False
                return ret
            # Step 1: Get the first possible date
            dat, ok = self.popDate(d1, d1)
            if ok:
                ret.append(dat)
            # Step 2: Get further dates not consumed so far
            while len(ret) < n:
                dat, ok = self.popDate(d1, d2)
                if not ok:
                    break
                ret.append(dat)
            # Step 3: Repeat dates already consumed
            while len(ret) < n:
                shft = np.random.randint(30)
                dat = d2 - pd.Timedelta(shft, 'D')
                if dat not in ret:   # Without repetitions
                    ret.append(dat)
            return ret
    

    根据日期用日期范围实例化此类的对象 在DF1

    dg = DateGen(DF1.date_1.min() - td30, DF1.date_1.max())
    

    这次的复制功能有点不同:

    def repl(row):
        d1 = row.date_1
        cnt = row['count']
        dates = np.sort(dg.popDates(d1 - td30, d1 - td1, cnt))
        return pd.DataFrame({'date_1': d1, 'count': cnt, 'date_2': dates})
    

    它从生成器对象中弹出日期,然后对它们进行排序并返回 在返回的 DataFrame 中。

    最后一步是应用它:

    DF2 = pd.concat(DF1.sort_values('date_1').\
        apply(repl, axis=1).tolist(), ignore_index=True)
    

    因为现在日期顺序很重要,我从 date_1 开始排序。

    【讨论】:

    • 感谢您的回复,但我收到了错误("unsupported operand type(s) for -: 'str' and 'Timedelta'", 'occurred at index 0')
    • 我还想有另一个约束,即 date_2 的所有最大值,即 date_2 应该具有最小值和最大值之间的所有值,即从 01/08/2019 到 30/09/2019(因为 date_1 有范围从 2019 年 1 月 8 日至 2019 年 9 月 31 日)。我们也可以在这里合并吗?
    • 我编写代码时假设 date_1datetime 类型。如果不是这种情况,例如你把它读成字符串,从转换它开始。
    • 我将它转换为日期时间,你能看看其他评论吗,即 date_2 应该具有最小值和最大值之间的所有值,即从 2019 年 1 月 8 日到 2019 年 9 月 30 日(自date_1 的范围从 2019 年 1 月 8 日到 2019 年 9 月 31 日)。我们也可以在这里合并吗?
    • 你能看看这个吗?
    【解决方案2】:

    我会使用不同的方法。制作一个函数,将数据框的一行作为参数和允许的日期,从中选择随机日期的数量,并让它返回给定行数的数据框。您可以使用apply 在每一行上使用此功能。然后你可以连接所有这些数据帧。

    为了不生成重复的'date_2' 值,请使用numpy random shuffle。它将随机更改数组中元素的顺序。然后,您可以只选择前 n 个元素。

    这里我还使用pandas date_range 来生成从中选择随机日期的日期范围。这样做一次(效率更高,因为它们始终相同),然后当apply 调用时,日期将传递给makedate2 函数。

    def makedate2(row, dates):
        cnt = row['count']
        np.random.shuffle(dates) #randomly change the order of dates
        return pd.DataFrame({'date_1':row['date_1'],
                             'count':cnt,
                             'date_2':dates[:cnt]}
                           )
    
    alldates = pd.date_range(df['date_1'].min() - pd.Timedelta(30, unit='D'), df['date_1'].max() - pd.Timedelta(30, unit='D')).to_numpy()
    res = df.apply(lambda x : makedate2(x, alldates), axis=1)
    df2 = pd.concat(res.to_numpy()).reset_index(drop=True)
    

    使用您提供的示例数据可能的df2 是:

            date_1  count     date_2
    0   2019-01-09      5 2019-09-26
    1   2019-01-09      5 2019-09-11
    2   2019-01-09      5 2019-05-18
    3   2019-01-09      5 2019-10-15
    4   2019-01-09      5 2019-06-06
    ..         ...    ...        ...
    130 2019-09-14     16 2019-04-12
    131 2019-09-14     16 2019-04-05
    132 2019-09-14     16 2019-10-08
    133 2019-09-14     16 2019-05-05
    134 2019-09-14     16 2019-11-09
    

    【讨论】:

    • 感谢您的回答,我想再定义 1 个约束,即 date_2 应具有最小值和最大值之间的所有值,即从 2019 年 1 月 8 日到 2019 年 9 月 30 日(因为 date_1 有范围从 2019 年 1 月 8 日至 2019 年 9 月 31 日)。我们也可以在这里合并吗?
    • 这个额外的约束对我来说没有多大意义。如果您想从 date_1 之前 30 天中选择日期,将最小值设置为 date_1 中的最小日期意味着 date_1 中的最小日期将没有 date_2 的可能值
    • 我想你误解了..我想说的是 date_1 有值(01/09/2019-31/09/2019),date_2 应该有(01/08/ 2019-30/09/2019) 对于 date_1 (01/09/2019),date_2 可以具有的最小值是 01/08/2019,对于 date_1 (31/09/2019),date_2 可以具有最大值的日期是 30 /09/2019。我想要 date_2 (01/08/2019 - 30/09/2019)之间的所有值
    • 所以基本上你希望所有行从相同的时间间隔[min(date_1) - 30days, max(date_1) - 1day] 中选择随机日期?
    • 没错,这正是我想要的
    猜你喜欢
    • 1970-01-01
    • 2019-12-30
    • 2013-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    相关资源
    最近更新 更多