Как консолидировать список посещаемости из MS Teams? - PullRequest
5 голосов
/ 03 августа 2020

Недавно Microsoft объявила о возможности загрузки данных о посещаемости в группах MS, чтобы отслеживать, кто присоединяется, а кто уходит. Но для определения посещаемости в группе класса это не совсем полезно, поскольку не дает надлежащего способа узнать, сколько времени участник находится на собрании.

Например,

df = pd.DataFrame([["Organiser Name","Joined","03/08/2020, 16:30:41"],
["Organiser Name","Left","03/08/2020, 17:03:32"],
["Organiser Name","Joined","03/08/2020, 17:04:25"],
["Student 1","Joined before","03/08/2020, 16:30:41"],
["Student 1","Joined before","03/08/2020, 17:04:27"],
["Student 2","Joined before","03/08/2020, 16:30:41"],
["Student 2","Joined","03/08/2020, 17:04:27"],
["Student 3","Joined","03/08/2020, 16:31:47"],
["Student 3","Joined","03/08/2020, 17:04:27"],
["Student 3","Left","03/08/2020, 17:30:32"],
["Student 4","Joined","03/08/2020, 16:32:01"],
["Student 4","Left","03/08/2020, 16:37:20"],
["Student 4","Joined","03/08/2020, 16:39:27"],
["Student 4","Joined","03/08/2020, 17:04:27"],
["Student 4","Left","03/08/2020, 17:17:19"],
["Student 4","Joined","03/08/2020, 17:19:13"],
["Student 5","Joined","03/08/2020, 16:35:41"],
["Student 5","Left","03/08/2020, 16:36:46"],
["Student 6","Joined","03/08/2020, 16:38:01"],
["Student 6","Left","03/08/2020, 16:40:14"],
["Student 7","Joined","03/08/2020, 17:15:08"],
["Student 7","Left","03/08/2020, 17:15:44"],
["Student 7","Joined","03/08/2020, 17:15:48"],
["Student 7","Left","03/08/2020, 17:15:54"],
["Student 8","Joined","03/08/2020, 17:18:12"],
["Student 8","Left","03/08/2020, 17:19:59"]], columns = ["Full Name","User Action","Timestamp"])

Это исходный список присутствующих на собрании (заменены только имена). Предположим, я заканчиваю собрание в 03/08/2020, 17:22:00 и загружаю данные о посещаемости на 2 минуты раньше. Могу ли я придумать способ суммирования этих данных с помощью python? Например,

Список учеников и его / ее продолжительность, в течение которой он был в классе

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

Я не имею в виду какой-либо базовый код, так как меня смущает следующее:

  1. В промежутках я пропускаю некоторое оставшееся время, но есть два последовательных времени присоединения
  2. Когда я выхожу из собрания из-за каких-то rnet проблем и повторно присоединяюсь, то информации нет о том, кто ушел и присоединился между мной, уходя и присоединяясь.

Кто-нибудь решил эту проблему?

Или есть идея обобщить эти данные?

или любой другой sh идея принять решение о правомочности посещения?

Заранее спасибо.

Примечание: Я ожидаю, что я использую python, однако решение в любом язык или в Excel также ар е добро пожаловать.

Ответы [ 4 ]

0 голосов
/ 09 августа 2020

Это решение обрабатывает упорядоченные события и отслеживает состояние (настоящее или нет) для всех, кто появляется.

Основные допущения:

  • подсчитывается только время с присутствием владельца данных (например, «Имя организатора»)
  • окончание сеанса - последнее записанное событие

Остальные примечания в коде.

Проверка правильности ответа: Организатор присоединился в 16:30:41, максимальная отметка времени - 17:30:32, а органайзер отсутствовал примерно на минуту, поэтому максимально возможная продолжительность составляет около 59 минут, после logi c @above_c_level для общего времени вместе.

import pandas as pd


class MeetingMonitor:

    def __init__(self, df, owner):
        df.columns = ["who", "action", "timestamp"]
        df['action'] = df['action'].replace('Joined.*', 1, regex=True)
        df['action'] = df['action'].replace('Left.*', 0, regex=True)
        df['timestamp'] = pd.to_datetime(df['timestamp'])
        self.owner = owner
        self.min_join = df.loc[df['who'] == owner, 'timestamp'].min()
        df = df.sort_values('timestamp')
        self.df = df
        self.folks = {}

    def get_report(self):
        self.folks = {}
        self.df.apply(self.handle, axis=1)
        # no data on true end of session, so best guest is last event
        self.everybody_leaves(df['timestamp'].max())
        results = [(self.folks[folk]['who'], self.folks[folk]['duration'])
                for folk in self.folks.keys()]
        results = pd.DataFrame(results, columns=['who', 'duration'])
        results['slack'] = results.duration.max() - results.duration
        return results.sort_values('slack')

    def make_folk(self, event):
        folk = {
            'who': event['who'],
            'duration': pd.Timedelta(0),
            'state': 1,
            'in': max(event.timestamp, self.min_join)
        }
        self.folks[folk['who']] = folk

    def join(self, event):
        self.folks[event['who']]['state'] = 1
        self.folks[event['who']]['in'] = event.timestamp

    def leave(self, who, timestamp):
        if self.folks[who]['state'] == 0:  # everybody leaves
            return
        self.folks[who]['duration'] += timestamp - self.folks[who]['in']
        self.folks[who]['state'] = 0

    def everybody_leaves(self, timestamp):
        for folk in self.folks.keys():
            self.leave(folk, timestamp)

    def handle(self, event):
        if event.who not in self.folks:
            if event.action == 1:
                self.make_folk(event)
                return 1
            else:
                pass  # someone left who wasn't here ... ok
        elif event.action == self.folks[event.who]['state']:
            # this shouldn't happen, mostly because of "everybody leaves" below
            # asymmetric assumption for bad data here,
            #   biased in favor of double joiners *shrug*
            return 1
        elif event.action == 1:
            self.join(event)
            return 1
        elif event.action == 0:
            if event.who == self.owner:
                self.everybody_leaves(event.timestamp)
            else:
                self.leave(event.who, event.timestamp)
            return 1

        # https://waffleguppies.tumblr.com/post/50741279401/just-a-reminder-that-the-nuclear-tesuji-is-a
        raise ValueError("(ノಠ益ಠ)ノ彡" + str(event))


df = pd.DataFrame([["Organiser Name", "Joined", "03/08/2020, 16:30:41"],
                   ["Organiser Name", "Left", "03/08/2020, 17:03:32"],
                   ["Organiser Name", "Joined", "03/08/2020, 17:04:25"],
                   ["Student 1", "Joined before", "03/08/2020, 16:30:41"],
                   ["Student 1", "Joined before", "03/08/2020, 17:04:27"],
                   ["Student 2", "Joined before", "03/08/2020, 16:30:41"],
                   ["Student 2", "Joined", "03/08/2020, 17:04:27"],
                   ["Student 3", "Joined", "03/08/2020, 16:31:47"],
                   ["Student 3", "Joined", "03/08/2020, 17:04:27"],
                   ["Student 3", "Left", "03/08/2020, 17:30:32"],
                   ["Student 4", "Joined", "03/08/2020, 16:32:01"],
                   ["Student 4", "Left", "03/08/2020, 16:37:20"],
                   ["Student 4", "Joined", "03/08/2020, 16:39:27"],
                   ["Student 4", "Joined", "03/08/2020, 17:04:27"],
                   ["Student 4", "Left", "03/08/2020, 17:17:19"],
                   ["Student 4", "Joined", "03/08/2020, 17:19:13"],
                   ["Student 5", "Joined", "03/08/2020, 16:35:41"],
                   ["Student 5", "Left", "03/08/2020, 16:36:46"],
                   ["Student 6", "Joined", "03/08/2020, 16:38:01"],
                   ["Student 6", "Left", "03/08/2020, 16:40:14"],
                   ["Student 7", "Joined", "03/08/2020, 17:15:08"],
                   ["Student 7", "Left", "03/08/2020, 17:15:44"],
                   ["Student 7", "Joined", "03/08/2020, 17:15:48"],
                   ["Student 7", "Left", "03/08/2020, 17:15:54"],
                   ["Student 8", "Joined", "03/08/2020, 17:18:12"],
                   ["Student 8", "Left", "03/08/2020, 17:19:59"]], columns=["Full Name", "User Action", "Timestamp"])

# don't assume data will be nicely ordered, make user specify the owner
mm = MeetingMonitor(df, df.iloc[0, 0])
res = mm.get_report()
print(res)

Вывод:

              who        duration           slack
0  Organiser Name 0 days 00:58:58 0 days 00:00:00
1       Student 1 0 days 00:58:56 0 days 00:00:02
2       Student 2 0 days 00:58:56 0 days 00:00:02
3       Student 3 0 days 00:57:50 0 days 00:01:08
4       Student 4 0 days 00:53:35 0 days 00:05:23
6       Student 6 0 days 00:02:13 0 days 00:56:45
8       Student 8 0 days 00:01:47 0 days 00:57:11
5       Student 5 0 days 00:01:05 0 days 00:57:53
7       Student 7 0 days 00:00:42 0 days 00:58:16
0 голосов
/ 09 августа 2020

Важным моментом является разрешение вычислений дельты временных меток с использованием to_datetime(). Время посещаемости может быть просто суммировано в dict по участникам:

import pandas as pd

df = pd.DataFrame(...)  # as given


def main():
    col_n, col_a, col_t = df.columns  # just for readability: name, action, time
    i_n, i_a, i_t = range(3)
    df[col_t] = pd.to_datetime(df[col_t])  # for calculating
    meeting_end, meeting_begin = max(df[col_t]), min(df[col_t])
    meeting_duration_secs = (meeting_end - meeting_begin).total_seconds()
    names = sorted(set(df[col_n]))
    attendance = {}  # summarize time deltas per name
    for name in names:
        arr = df[df[col_n] == name].values  # ndarray of current attendee slice
        assert arr[0][i_a].startswith("Joined")
        attendance[name] = 0.
        for i in range(len(arr) - 1):
            row_1, row_2 = arr[i], arr[i+1]
            if row_1[i_a].startswith("Joined") and row_2[i_a] == "Left":
                attended = row_2[i_t] - row_1[i_t]
                attendance[name] += attended.total_seconds()
        if arr[-1][i_a] != "Left":
            attended = meeting_end - arr[-1][i_t]
            attendance[name] += attended.total_seconds()

    name_len = len(max(attendance, key=lambda s: len(s)))
    for name in attendance:
        mins = round(attendance[name] / 60., 1)
        perc = round(100. * attendance[name] / meeting_duration_secs, 1)
        print(f"{name:<{name_len}} {mins:5} min {perc:5} %")


if __name__ == '__main__':
    main()

... не уверен, что logi c охватывает все обстоятельства, например, действия могут иметь другие имена, чем ["Joined", "Joined before", "Left"], и независимо от того, выполняется ли оно, первая запись участника всегда startswith("Joined") - может быть, используйте meeting_begin, если нет.

0 голосов
/ 09 августа 2020

Определение посещаемости

Я думаю, что основная проблема здесь - это разумное определение посещаемости. Для конкретного c варианта использования учителя (= организатора) и учеников это просто:

«Посещаемость - это период, в течение которого присутствуют и учитель / организатор, и ученики».

Обоснование:

  • Если учащиеся входят в систему раньше, урок еще не начался. Они ни на что не ходят.
  • Если ученики выходят из системы, пока учитель находится в системе, они что-то пропустят.
  • Неважно, выйдут ли ученики из системы или войдут в нее, пока учитель далеко, они ничего не упускают. (Примечание: это главное различие между виртуальным и реальным классом.)

Именно так MS Teams думает о посещаемости. С веб-сайта MS Office :

Имейте в виду, что вы сможете загрузить отчет о посещаемости только во время встречи и присутствия участников. [...] Если участники присоединяются к собранию до организатора, время их присоединения будет соответствовать времени, когда организатор присоединился к собранию.

У данных есть одна большая проблема: время окончания собрания пропал, отсутствует. Организатор не может уйти, потому что тогда отчет нельзя будет скачать. Но организатор всегда будет скачивать после окончания урока. Вам придется указать дату окончания вручную или использовать разумный

ответ

С определением мы можем центрировать все периоды времени на органайзере. В коде много чего происходит. Я прокомментировал как можно лучше.

    # neccessary inputs (not in original data)
    end_timestamp = pd.Timestamp('2020-03-08 17:40')
    organizer_name = 'Organiser Name'

    # Pivot User Action values to columns; we don't need 'Join before'
    df['Timestamp'] = pd.to_datetime(df['Timestamp'])
    df['User Action'] = df['User Action'].str.replace('Joined before', 'Joined')
    df = df.set_index(['Full Name', 'User Action'], append=True).unstack()
    df.columns = df.columns.get_level_values(1)

    # we can (always) shift the 'Left' dates due to underlying data structure
    df['Left'] = df['Left'].shift(-1)
    df = df.dropna(how='all')

    # organizer can only have one missing value: the end value
    mask_organizer = df.index.get_level_values('Full Name') == organizer_name
    df.loc[mask_organizer, 'Left'] = df.loc[mask_organizer, 'Left'].fillna(end_timestamp)
    replace_na_dates = list(df.loc[mask_organizer, 'Left'])

    def fill_missing_dates(join_date, left_date, replace_dates):
        if left_date is not pd.NaT:
            return left_date
        for date in replace_dates:
            if join_date < date:
                return date
        return replace_dates[-1]

    df['Left'] = df.apply(lambda x: fill_missing_dates(x['Joined'], x['Left'], replace_na_dates), axis=1)
    df['Attendance'] = df['Left'] - df['Joined']
    df = df.groupby(level=1).sum()

Вывод:

                Attendance
Full Name                
Organiser Name   01:08:26
Student 1        01:08:24
Student 2        01:08:24
Student 3        00:57:50
Student 4        01:03:03
Student 5        00:01:05
Student 6        00:02:13
Student 7        00:00:42
Student 8        00:01:47

Вы можете заметить, что у организатора на две секунды больше посещаемости, чем у всех остальных. Я думаю, что MS Teams регистрирует присоединение организатора правильно, но требуется немного больше времени, чтобы получить отзывы от всех участников собрания. Другими словами: это время между фразами «Я вернулся» и «Теперь я вижу вас всех».

0 голосов
/ 07 августа 2020

Я старался изо всех сил, но у меня мало опыта работы с TimeSeries, так что, возможно, другие люди смогут его завершить. По сути, вам нужно поставить метку времени для своего столбца, чтобы Pandas понимал, что он имеет дело со временем, а не со строкой / объектом. Затем вам нужно повернуть его. Последний шаг - подсчитать время, в течение которого каждый ученик был на собрании. Надеюсь, это поможет вам начать работу.

import pandas as pd
df = pd.read_csv('vg.csv', delimiter=",", sep=",")
df.head()

df['Timestamp'] = pd.to_datetime(df.Timestamp)

df['Hour'] = df.Timestamp.dt.hour #converting it to hour
df['Minutes'] = df.Timestamp.dt.minute #converting it to minutes
df['Sek'] = df.Timestamp.dt.minute #converting it to sexcunds
df['Ended'] = "18:00:00"
df['Ended'] = pd.to_datetime(df.Ended)

df.rename(columns={'   Full Name': 'Person'}, inplace=True) #Sorry I wanted to change your Columns name

result = df.pivot_table(index='Person',
                         columns='User Action',
                         values=['Hour', 'Minutes', "Sek", "Ended"])
print(result)
#

введите описание изображения здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...