Создание итеративных дат в фрейме данных Pandas

#python #pandas #dataframe #datetime #data-science

#python #pandas #фрейм данных #дата и время #наука о данных

Вопрос:

У меня есть постановка проблемы следующим образом:

В каждом экзаменационном центре экзамен должен быть организован в две смены, партия I и партия II (отчетное время 9:00 утра и 2 часа дня). Экзамен может быть проведен в любой день в округе с 1 по 30 декабря 2020 года в зависимости от количества кандидатов в округе. Обратите внимание, что в каждом районе возможен только один экзаменационный центр, и за одну смену может появиться максимум 20 студентов. На основе упомянутой выше информации заполните базу данных проверки, выделив:

  • Rollno: номер броска кандидата будет начинаться с NL2000001 и далее (например: NL2000001, NL2000002, NL2000003 ……)
  • cent_allot: выделение центра путем ввода кода города проверки
  • cent_add: укажите NL «Название района» в качестве адреса центра в каждом местоположении (например, если название района равно ADI, тогда добавление центра равно NL ADI)
  • Дата экзамена: назначьте любую дату экзамена в период с 1 декабря 2020 года по 30 декабря 2020 года, сохраняя минимальное количество экзаменационных дней и не нарушая никаких условий, упомянутых выше
  • пакет: выделение пакета I или II, обеспечивающего все условия, упомянутые выше
  • rep_time: для пакета I время отчетности составляет 9 часов утра, а для пакета II время отчетности — 2 часа дня.

Согласно приведенному выше описанию, мне нужно создать таблицу, которая удовлетворяет вышеуказанным условиям. Я уже создал столбцы Rollno, cent_allot и cent_add, но мне трудно создать столбец examDate, поскольку он должен иметь одну и ту же дату для каждых 40 значений district.

Вот список районов и их частота встречаемости:

 Dist    Count
WGL     299
MAHB    289
KUN     249
GUN     198
KARN    196
KRS     171
CTT     169
VIZ     150
PRA     145
NALG    130
MED     128
ADI     123
KPM     119
TRI     107
ANA     107
KHAM    85
NEL     85
VIZI    84
EGOD    84
SOA     84
SIR     80
NIZA    73
PUD     70
KRK     69
WGOD    56
  

Вот первые 25 строк фрейма данных:

 Rollno     cent_allot   cent_add    examDate    batch   rep_time
NL2000001   WGL          NL WGL       NaN        NaN    NaN
NL2000002   WGL          NL WGL       NaN        NaN    NaN
NL2000003   WGL          NL WGL       NaN        NaN    NaN
NL2000004   KUN          NL KUN       NaN        NaN    NaN
NL2000005   KUN          NL KUN       NaN        NaN    NaN
NL2000006   KUN          NL KUN       NaN        NaN    NaN
NL2000007   GUN          NL GUN       NaN        NaN    NaN
NL2000008   GUN          NL GUN       NaN        NaN    NaN
NL2000009   GUN          NL GUN       NaN        NaN    NaN
NL2000010   GUN          NL GUN       NaN        NaN    NaN
NL2000011   VIZ          NL VIZ       NaN        NaN    NaN
NL2000012   VIZ          NL VIZ       NaN        NaN    NaN
NL2000013   VIZ          NL VIZ       NaN        NaN    NaN
NL2000014   VIZ          NL VIZ       NaN        NaN    NaN
NL2000015   MAHB         NL MAHB      NaN        NaN    NaN
NL2000016   MAHB         NL MAHB      NaN        NaN    NaN
NL2000017   MAHB         NL MAHB      NaN        NaN    NaN
NL2000018   WGOD         NL WGOD      NaN        NaN    NaN
NL2000019   WGOD         NL WGOD      NaN        NaN    NaN
NL2000020   WGOD         NL WGOD      NaN        NaN    NaN
NL2000021   WGOD         NL WGOD      NaN        NaN    NaN
NL2000022   EGOD         NL EGOD      NaN        NaN    NaN
NL2000023   EGOD         NL EGOD      NaN        NaN    NaN
NL2000024   EGOD         NL EGOD      NaN        NaN    NaN
NL2000025   EGOD         NL EGOD      NaN        NaN    NaN
  

Все последние 3 столбца — это NaN s, поскольку эти три столбца еще предстоит создать.

Давайте возьмем WGL , к примеру. Согласно приведенному выше описанию, в смену для каждого района может быть разрешено не более 20 кандидатов, что означает, что одна и та же дата должна быть выделена 40 раз для каждого района, и одна и та же партия и одинаковое время отчетности должны быть выделены 20 раз для каждого округа.

У кого-нибудь есть идеи, как это сделать?

Ответ №1:

Ключ должен использоваться .groupby().cumcount() для получения текущего номера первым. Впоследствии examDate и batch может быть определено соответственно по модулю текущего числа против 40 и 20.

Данные

Случайные строки генерируются с использованием заданного общего количества для каждой Dist .

 import numpy as np
import pandas as pd
import io
import datetime

df_count = pd.read_csv(io.StringIO("""
Dist    Count
WGL     299
MAHB    289
KUN     249
GUN     198
KARN    196
KRS     171
CTT     169
VIZ     150
PRA     145
NALG    130
MED     128
ADI     123
KPM     119
TRI     107
ANA     107
KHAM    85
NEL     85
VIZI    84
EGOD    84
SOA     84
SIR     80
NIZA    73
PUD     70
KRK     69
WGOD    56
"""), sep=r"s{2,}", engine="python")

# generate random cent_allot
df = df_count.loc[np.repeat(df_count.index.values, df_count["Count"]), "Dist"]
    .sample(frac=1)
    .reset_index(drop=True)
    .to_frame()
    .rename(columns={"Dist": "cent_allot"})

df["Rollno"] = df.index.map(lambda s: f"NL2{s 1:06}")
df["cent_add"] = df["cent_allot"].map(lambda s: f"NL {s}")
  

df до сих пор должно напоминать то, что у вас было.

Код

 # Assign the first examDate
first_day = datetime.date(2020, 12, 1)

# running no. grouped by "cent_allot" (i.e. "Dist")
df["gp_no"] = df.groupby("cent_allot").cumcount()

# increase one day for every 40 records
df["examDate"] = df["gp_no"].apply(lambda x: first_day   datetime.timedelta(days=int(x / 40)))

# batch - can be determined by the even-ness of int(no. / 20)
df["batch"] = df["gp_no"].apply(lambda x: 1   int(x / 20) % 2)

# map batch to time (or "9 AM" / "2 PM" as you'd like)
df["rep_time"] = df["batch"].apply(lambda x: datetime.time(9, 0) if x == 1 else datetime.time(14, 0))
  

Вывод

 print(df[["Rollno", "cent_allot", "cent_add", "examDate", "batch", "rep_time"]])

         Rollno cent_allot cent_add    examDate  batch  rep_time
0     NL2000001        CTT   NL CTT  2020-12-01      1  09:00:00
1     NL2000002       MAHB  NL MAHB  2020-12-01      1  09:00:00
2     NL2000003        CTT   NL CTT  2020-12-01      1  09:00:00
3     NL2000004        SOA   NL SOA  2020-12-01      1  09:00:00
4     NL2000005        PUD   NL PUD  2020-12-01      1  09:00:00
         ...        ...      ...         ...    ...       ...
3345  NL2003346       KHAM  NL KHAM  2020-12-03      1  09:00:00
3346  NL2003347        ADI   NL ADI  2020-12-04      1  09:00:00
3347  NL2003348       KARN  NL KARN  2020-12-05      2  14:00:00
3348  NL2003349        SIR   NL SIR  2020-12-02      2  14:00:00
3349  NL2003350        ADI   NL ADI  2020-12-04      1  09:00:00

[3350 rows x 6 columns]
  

Комментарии:

1. спасибо за ваш код. Ваш код выглядит лаконичным и уместным. Я не запускал ваш код, так как сам решил, что это утомительное решение, но все же я очень ценю помощь тура. Спасибо за вашу помощь…

Ответ №2:

Я много боролся за получение решения, но, наконец, в конце того дня, когда я задал вопрос, я нашел решение:

 # examDate column

n_stud = 20   # mention the number of students per batch here
n_batch = 2   # mention the number of batches per day here

temp = data['TH_CENT_CH'].value_counts().sort_index().reset_index()  # storing centers and their counts in a temp variable
cent = temp['index'].to_list()      # storing centers in a list
cnt = temp['TH_CENT_CH'].to_list()  # storing counts in a list
cent1 = []
cnt1 = []
j = 0

# for loops to repeat each center by count times
for c in cent:
    for i in range(1, cnt[j]   1):
        cent1.append(c)
        cnt1.append(i)
    j  = 1

df1 = pd.DataFrame(list(zip(cent1, cnt1)), columns = ['cent','cnt'])  # dataframe to store the centers and new count list

counts = df1['cnt'].to_list() # storing the new counts in a list
helper = {}  # helper dictionary
max_no = max(cnt)

# for-while loops to map helper number to each counts number
for i in counts:
    j = 0
    while(j < (round(max_no / (n_stud * n_batch))   1)):
        if((i > (n_stud * n_batch * j)) amp; (i < (n_stud * n_batch * (i   1)))):
            helper[i] = j
        j  = 1

# mapping the helper with counts
counts = pd.Series(counts)
helper = pd.Series(helper)
hel = counts.map(helper).to_list()
df1['helper'] = hel

examDate = {}  # dictionary to store exam dates

# for loop to map dates to each helper number
for i in hel:
    examDate[i] = pd.to_datetime(date(2020, 12, 1)   timedelta(days = (2 * i)))

# mapping the dates with helpers
hel = pd.Series(hel)
examDate = pd.Series(examDate)
exam = hel.map(examDate).to_list()
df1['examDate'] = exam
        
# adding the dates to the original dataframe
examDate = df1['examDate'].to_list()
data['examDate'] = examDate
data['examDate']
  

Здесь TH_CENT_CH относится к столбцу district в исходном фрейме данных. Когда я запустил data.head() , я получил результат, который мне был нужен, то есть одну дату для 40 студентов. Я сделал то же самое для оставшихся двух столбцов, где мне нужно было иметь одинаковую партию для 20 студентов. И поэтому я получил вывод в виде:

         Rollno  cent_allot  cent_add  examDate   batch  rep_time
0     NL2000001        ADI   NL ADI  2020-12-01      1  09:00:00
1     NL2000002        ADI   NL ADI  2020-12-01      1  09:00:00
2     NL2000003        ADI   NL ADI  2020-12-01      1  09:00:00
3     NL2000004        ADI   NL ADI  2020-12-01      1  09:00:00
4     NL2000005        ADI   NL ADI  2020-12-01      1  09:00:00
         ...        ...      ...         ...    ...       ...
3345  NL2003346        WGOD  NL WGOD 2020-12-03      1  09:00:00
3346  NL2003347        WGOD  NL WGOD 2020-12-04      1  09:00:00
3347  NL2003348         KRS  NL KRS  2020-12-05      1  09:00:00
3348  NL2003349        WGOD  NL WGOD 2020-12-02      1  09:00:00
3349  NL2003350        WGOD  NL WGOD 2020-12-04      1  09:00:00
  

Пожалуйста, найдите код для оставшихся двух столбцов:

 # batch column

counts = df1['cnt'].to_list()  # storing the new counts in a list
helper2 = {}  # helper dictionary

# for-while loops to map helper number to each counts number
for i in counts:
    j = 0
    while(j < (round(max_no / (n_stud))   1)):
        if((i > (n_stud * j)) amp; (i < (n_stud * (i   1)))):
            helper2[i] = j
        j  = 1

# mapping the helper with counts
counts = pd.Series(counts)
helper2 = pd.Series(helper2)
hel2 = counts.map(helper2).to_list()
df1['helper2'] = hel2

batch = {}   # dictionary to store batch numbers

# for loop to map batch numbers to each helper number
for i in hel2:
    if(i % 2 == 0):
        batch[i] = 1
    else:
        batch[i] = 2
        
# mapping the batches with helpers
hel2 = pd.Series(hel2)
batch = pd.Series(batch)
bat = hel2.map(batch).to_list()
df1['batch'] = bat

# adding the batches to the original dataframe
batch = df1['batch'].to_list()
data['batch'] = batch
data['batch'].unique()

# rep_time column
data.loc[data['batch'] == 1, 'rep_time'] = '9:00 AM'
data.loc[data['batch'] == 2, 'rep_time'] = '2:00 PM'
data['rep_time'].unique()