Определите следующую функцию «репликации»:
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 этогоимя. На самом деле это просто пример того, как не назначать имена столбцов. Вы должны не использовать имена существующих методов.
Редактировать после комментария о "всех значениях"
Чтобы использовать все даты из диапазона, процедура более сложна ивключает создание выделенного класса для генерации дат из пула.
Алгоритм назначения даты выглядит следующим образом:
- Пул дат создается в начале, начиная с мин. date - 30 дней до max date.
- При каждом вызове:
- Шаг 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 .