Как я могу построить более быстро убывающее среднее? сравнивая поле даты строк фрейма данных с датами других строк - PullRequest
0 голосов
/ 07 апреля 2020

Я неуклюж, но адекватен с python. Я часто ссылаюсь на стек, но это мой первый вопрос. Я построил функцию усредняющего затухания, которая воздействует на фрейм данных pandas с примерно 10000 строками, но для запуска требуется 40 минут. Буду признателен за любые мысли о том, как ускорить его. Вот образец фактических данных, немного упрощенный.

sub = pd.DataFrame({
        'user_id':[101,101,101,101,101,102,101],
        'class_section':['Modern Biology - B','Spanish Novice 1 - D', 'Modern Biology - B','Spanish Novice 1 - D','Spanish Novice 1 - D','Modern Biology - B','Spanish Novice 1 - D'],
        'sub_skill':['A','A','B','B','B','B','B'],
        'rating' :[2.0,3.0,3.0,2.0,3.0,2.0,2.0],
        'date' :['2019-10-16','2019-09-04','2019-09-04', '2019-09-04','2019-09-13','2019-10-16','2019-09-05']})

Для этого фрейма данных:

sub
Out[716]: 
   user_id            class_section sub_skill  rating        date
0      101       Modern Biology - B         A     2.0  2019-10-16
1      101     Spanish Novice 1 - D         A     3.0  2019-09-04
2      101       Modern Biology - B         B     3.0  2019-09-04
3      101     Spanish Novice 1 - D         B     2.0  2019-09-04
4      101     Spanish Novice 1 - D         B     3.0  2019-09-13
5      102       Modern Biology - B         B     2.0  2019-10-16
6      101     Spanish Novice 1 - D         B     2.0  2019-09-05

Затухающее среднее взвешивает самое последнее событие, которое соответствует условиям в полном весе и взвешивает каждое предыдущее событие с множителем меньше единицы. В этом случае множитель составляет 0,667. ранее взвешенные события снова взвешиваются.

Таким образом, среднее значение затухания для рейтинга пользователя 101 в Spani sh sub_skill B составляет:

(2,0 * 0,667 ^ 2 + 2,0 * 0,667 ^ 1 + 3,0 * 0,667 ^ 0) / ((0,667 ^ 2 + 0,667 ^ 1 + 0,667 ^ 0) = 2,4735

Вот что я попробовал, прочитав полезный пост о средневзвешенных значениях

sub['date'] = pd.to_datetime(sub.date_due) 

def func(date, user_id, class_section, sub_skill):
    return sub.apply(lambda row: row['date'] > date  
                     and row['user_id']==user_id 
                     and row['class_section']== class_section 
                     and row['sub_skill']==sub_skill,axis=1).sum()

# for some reason this next line of code took about 40 minutes to run on 9000 rows:
sub['decay_count']=sub.apply(lambda row: func(row['date'],row['user_id'], row['class_section'], row['sub_skill']), axis=1)

# calculate decay factor:
sub['decay_weight']=sub.apply(lambda row: 0.667**row['decay_count'], axis=1)

# calcuate decay average contributors (still needs to be summed):
g = sub.groupby(['user_id','class_section','sub_skill'])
sub['decay_avg'] = sub.decay_weight / g.decay_weight.transform("sum") * sub.rating

# new dataframe with indicator/course summaries as decaying average (note the sum):
indicator_summary = g.decay_avg.sum().to_frame(name = 'DAvg').reset_index()

Я часто работаю в pandas, и я привык к итерации больших наборов данных. Я ожидал бы, что это займет время в квадрате строк, но это займет гораздо больше времени. Более элегантное решение или несколько советов очень хотелось бы ускорить его!

Некоторые сведения об этом проекте: я пытаюсь автоматизировать переход от оценки на основе квалификации к классу c для моей школы. У меня есть процесс извлечение данных из нашей системы управления обучением в электронную таблицу, которая вычисляет среднее значение затухания, а затем отправляет информацию учителям, но я хотел бы автоматизировать весь процесс и извлечь себя из него. LMS не спешит внедрять систему, основанную на умениях, и неохотно обеспечивает преобразование - по уважительной причине. Тем не менее, мы должны сообщить родителям и колледжам как уровень знаний учащихся, так и их переход в традиционный класс, поскольку на этом языке они говорят.

1 Ответ

0 голосов
/ 07 апреля 2020

Почему бы не использовать groupby? Идея состоит в том, что вы ранжируете даты в группе в порядке убывания и вычитаете 1 (потому что ранг начинается с 1). Похоже, это отражает вашу логику c в func выше, без необходимости вызова применения с вложенным применением.

sub['decay_count'] = sub.groupby(['user_id', 'class_section', 'sub_skill'])['date'].rank(method='first', ascending=False) - 1

sub['decay_weight'] = sub['decay_count'].apply(lambda x: 0.667 ** x)

Вывод:

sub.sort_values(['user_id', 'class_section', 'sub_skill', 'decay_count'])                                      

   user_id         class_section sub_skill  rating       date  decay_count  decay_weight
0      101    Modern Biology - B         A     2.0 2019-10-16          0.0      1.000000
2      101    Modern Biology - B         B     3.0 2019-09-04          0.0      1.000000
1      101  Spanish Novice 1 - D         A     3.0 2019-09-04          0.0      1.000000
3      101  Spanish Novice 1 - D         B     2.0 2019-09-04          0.0      1.000000
6      101  Spanish Novice 1 - D         B     2.0 2019-09-05          1.0      0.667000
4      101  Spanish Novice 1 - D         B     3.0 2019-09-13          2.0      0.444889
5      102    Modern Biology - B         B     2.0 2019-10-16          0.0      1.000000
...