knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
Functions in {zoomclass} package are divided in 2 categories which aims to analyse input from (mainly) Zoom's participants report .csv
and Zoom's chat .txt
file.
library(zoomclass)
In this category, High-level functions are design to analyse Zoom's participants report (.csv
file), especially for checking student's attendance in Zoom classroom.
The term I used "Zoom's participant report", basically, is a CSV log file that host can downloaded after Zoom meeting has ended. It contains the following columns:
Leave Time
- Join Time
of each sessionThis package comes with an example data of Zoom's participant report in .csv
which I will use for demo in this tutorial.
# List all example data in `zoomclass` package zoomclass_example() # Get a path to specific example data path_heroes <- zoomclass_example("participants_heroes.csv") path_heroes
Path to all example files can be obtained by the following command.
# if (interactive() && requireNamespace("fs")) { # fs::file_show(system.file("extdata", package = "zoomclass")) # }
system.file("extdata", package = "zoomclass")
read_participants()
read the data in using readr::read_csv()
and then performs the followings steps:
R
Name
and Name_Original
columns, respectively.The output is a tibble with zoom_participants
subclass. If metadata regarding Zoom meeting was found, it will be assigned to a meeting_overview
attribute.
pp_heroes <- read_participants(path_heroes) str(pp_heroes)
As you can see, almost all column names were cleaned with no spaces; Join_Time
and Leave_Time
were formatted as POSIXct object, correctly. Moreover, "015_Loki (Loki Laufeyson)" in original Name (Original Name)
column is extracted into "015_Loki" in Name
and "Loki Laufeyson" in Name_Original
, respectively.
The output tibble pp_heroes
contain sessions information of each participants. If we count names of each participants, we can see that some rows is duplicated.
library(dplyr)
pp_heroes %>% count(`Name (Original Name)`, sort = TRUE) %>% head()
For example, "Power Girl" has 2 rows because she had 2 sessions in Zoom (join and leave 2 times).
pp_heroes %>% filter(Name == "003_Power Girl") %>% select(Name, Email, Join_Time, Leave_Time, Duration_Minutes)
But why Join_Time
and Leave_Time
is overlapped in these 2 sessions? This might be the case that she's joined Zoom with 2 devices at the same time. As I will show you next, The upcoming functions in this package can detect this scenario.
Now that we have read Zoom's participants data in, It's time to analyse participants in a context of a classroom. From now on, I'll call a "participant" as "student".
Next, 3 class_*
functions will be introduced:
class_session()
summarizes time information about individual sessions of each students (If student has multiple sessions, output will show ≥ 1 rows per that student)class_students()
summarizes time information of each students (1 row per student)class_studentsID()
summarizes time information of each student's ID extracted from student name (1 row per student's ID)The first argument of these functions receives input from zoom_participants
tibble as created by read_participants()
.
A typical academic classroom usually has an explicit start and end time. If students arrive to class later than certain cutoff time point, teacher can mark them as late.
In the class_*
functions, you must provide class_start
and class_end
arguments by which they will used to compute 4 time intervals that student spent before, during, and after Zoom class:
Before_Class
: represent time interval that student spent in Zoom before class startedDuring_Class
: represent time interval that student spent in Zoom during classAfter_Class
: represent time interval that student spent in Zoom after class endedTotal_Time
: the sum of time Before_Class
+ During_Class
+ After_Class
For class_students()
and class_studentsID()
, you can compute late time period of each student by providing late_cutoff
argument.
All of theses time intervals are Period object from {lubridate}
package. (It can be converted to hours, minutes, or secound as well.)
Furthermore, class_*
functions can tell you whether each students joined Zoom with multiple devices at the same time period. The use case of this might be when live-exam is conducted in Zoom, and you want to check that each students joined with 1 device only (no cheating).
Suppose that our Zoom classroom was started at 10:00 and ended at 12:00, I will call class_session()
as follows:
(Input class_start
and class_end
as 24-hours clock time with no AM or PM)
pp_heroes_session <- class_session(pp_heroes, class_start = "10:00", # Official class started at 10:00 AM class_end = "12:00" # Official class ended at 12:00 PM ) pp_heroes_session
As previously stated, classroom-related time intervals were computed.
pp_heroes_session %>% select(Name, ends_with("Class"), Total_Time) %>% head()
Let's see who spend time during class all the time
pp_heroes_session %>% filter(During_Class == lubridate::hours(2)) %>% select(Name, ends_with("Class"))
Or, may be Loki could cast illusion to attend Zoom till the end :-)
Now, let's see who attend Zoom using multiple device at the same time by filter Multi_Device = TRUE
.
pp_heroes_session %>% filter(Multi_Device == TRUE) %>% select(Name, Session, Join_Time, Leave_Time, Multi_Device)
Session
column displays session number of each student as ranked by Join_Time
. Let's see who joined Zoom more than 1 time.
As you can see, "Power Girl" and "Swarm" has session number = 2; however, be aware that these two joined Zoom only one time using 2 devices.
pp_heroes_session %>% filter(Session > 1) %>% select(Name, Session, Join_Time, Leave_Time, Multi_Device)
class_students()
summarizes time information grouped by each students (grouping variables is Name (Original Name)
and Email
).
The result will be a tibble with one row per student.
Most columns are the same as output from class_session()
, except for the followings:
Session_Count
: show session counts of each student (how many times each student join and leave class)First_Join_Time
: if student has multiple sessions, this would choose only the earliest join time.Last_Leave_Time
: if student has multiple sessions, this would choose only the latest leave time.New optional argument is late_cutoff
from which student will be considered late if first joined time is later than this cutoff. The late time period will be shown in Late_Time
column.
pp_heroes_students <- class_students(pp_heroes, class_start = "10:00", class_end = "12:00", late_cutoff = "10:15" # If student joined later than 10:15 will considered late ) head(pp_heroes_students)
Let's see who joined class late > 10 minutes (later than 10:25).
pp_heroes_students %>% filter(Late_Time > lubridate::minutes(10)) %>% select(Name, First_Join_Time, Late_Time)
Supposed that after you've checked class attendance of each student, perhaps you want to merge this data into a database by some key columns which is usually student's ID.
First, you informed students to put student's ID in their names, for example: "001_Megus".
class_studentsID()
will help you summarizes time information grouped by each student's ID. Then, you can used these IDs to merge into a database.
The internal processes are that class_studentsID()
extracts student's ID from Name (Original Name)
column using regular expression as provided by id_regex
. Then, time information will be summarized per student's ID (grouping variable is ID
), and the rest of output columns is similar to class_students()
. Finally, the result will be a tibble with one row per ID
.
pp_heroes_studentsID <- class_studentsID(pp_heroes, id_regex = "\\d+", # Extract digits from student name as student's ID class_start = "10:00", class_end = "12:00", late_cutoff = "10:15" # If student joined later than 10:15 will considered late ) head(pp_heroes_studentsID)
(If the same ID
is founded in multiple names (e.g. "001_Magus", "001_magus"), the Name
column will contain all combinations of names for that particular ID
.)
Here, I will give an example of merging student data to a database.
This package provides heroes_students
data frame that contain names and ID of student who enrolled the course in this semester.
head(heroes_students)
You can join heroes_students
with pp_heroes_studentsID
using dplyr::*_join
functions by ID
.
To check whether students joined Zoom class room or not, it can be obtained by using a filtering join function: dplyr::anti_join()
.
These students below are in the heroes_students
data frame, but not in pp_heroes_studentsID
, which means that they didn't join Zoom classroom.
heroes_students %>% anti_join(pp_heroes_studentsID, by = "ID")
Likewise, You can also check participants who joined Zoom classroom but not in the heroes_students
data frame (non-students). Again, dplyr::anti_join()
can be used.
pp_heroes_studentsID %>% anti_join(heroes_students, by = "ID")
dplyr::full_join()
is a mutating join function that merge 2 data frame without any rows lost from either one.
In this example, I fully joined 2 data frame pp_heroes_studentsID
and heroes_students
by ID
. Suffix "_from_ID" and "_from_Zoom" represents unmatched rows from the list of students (heroes_students
) and Zoom classroom, respectively.
# Merge to Zoom data by `ID` heroes_students_joined <- heroes_students %>% full_join(pp_heroes_studentsID, by = "ID", suffix = c("_from_ID", "_from_Zoom")) %>% relocate(ID, starts_with("Name")) head(heroes_students_joined)
Student who didn't joined Zoom classroom will have NA
presented in the Name_from_Zoom
column, whereas participants who joined Zoom classroom but not in the list of students will have NA
presented in the Name_from_ID
column.
heroes_students_joined %>% filter(if_any(starts_with("Name"), is.na)) %>% select(ID, starts_with("Name"), Comment)
You might want to parse Zoom chat file from .txt
file to a tibble.
# Path to example Zoom Chat file path <- zoomclass_example("zoom-chat-1.txt")
See that raw zoom-chat-1.txt
looks like this.
readLines(path) %>% glue::as_glue()
Use read_zoom_chat()
to read it into a data frame.
# Read from Text to a Data Frame read_zoom_chat(path)
Last updated: r Sys.Date()
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.