根据多个约束条件生成日期

时间:2019-11-03 14:58:07

标签: python pandas numpy

我有一个数据框df1,其日期为01的列,其值从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在01/09/2019将有5个条目(因为计数= 5),并且date_2列的值可以为date_1之前30天到30/08/2019(date_1-1中的当前日期) ),即对于01/09/2019,date_2可以采用的值是(01/09/2019-30 = 01/08/2019)到(01/09/2019-1 = 30/08/2019)。

可以从(30 - date_1date_1 - 1的范围中随机选择

date_2,即在我们的示例中,从01/08/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上生成相同的增量,从而导致条目重复。我不想生成重复的条目。 *

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

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

谢谢

2 个答案:

答案 0 :(得分:2)

定义以下“复制”功能:

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 的方法。 实际上,这只是作为示例,如何分配列名。 您应该使用现有方法的名称。

按照有关“所有值”的注释进行编辑

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

日期分配算法如下:

  • min 日期-30天到 max 日期。
  • 在每次通话中:
    • 第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 进行排序。

答案 1 :(得分:0)

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

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

在这里,我还使用pandas date_range生成日期范围,从中选择随机日期。只需执行一次(效率更高,因为它们总是相同的),然后由makedate2调用时,日期将传递给apply函数。

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