505 lines
23 KiB
Plaintext
505 lines
23 KiB
Plaintext
---
|
|
title: "Log data from the Multi-Touch Table at the HAUM"
|
|
output: github_document
|
|
---
|
|
|
|
```{r, include = FALSE}
|
|
devtools::load_all("../../../../software/mtt")
|
|
```
|
|
|
|
The Multi Touch Table at the Herzog-Anton-Ulrich-Museum (HAUM) in
|
|
Braunschweig gives visitors of the Museum the opportunity to interact with
|
|
about 70 artworks and 3 virtual cards containing information about the
|
|
museum and its layout. The table was installed at the museum in October
|
|
2016 and since November 2016 log files from interactions of visitors of the
|
|
museum have been collected. These log files are in an unstructured format
|
|
and cannot be easily analyzed. The purpose of the following document is to
|
|
describe how the data haven been transformed and which decisions have been
|
|
made along the way.
|
|
|
|
The implementation of the steps described here can be found at:
|
|
https://gitea.iwm-tuebingen.de/R/mtt.
|
|
|
|
# Data structure
|
|
|
|
The log files contain lines that indicate the beginning and end of possible
|
|
activities that can be performed when interacting with the artworks on the
|
|
table. The layout of the table looks like pictures have been tossed on a
|
|
large table. Every artwork is visible at the start configuration. People
|
|
can move the pictures on the table, they can be scaled and rotated.
|
|
Additionally, the virtual picture cards can be flipped in order to find
|
|
more information of the artwork on the "back" of the card. One has to press
|
|
a little `i` for more information in one of the bottom corners of the card.
|
|
On the back of the card two to six information cards can be found with a
|
|
teaser text about a certain topic. These topic cards can be opened and a
|
|
hypertext with detailed information opens. Within these hypertexts certain
|
|
technical terms can be clicked for lay people to get more information. This
|
|
also opens up a pop-up. The events encoded in the raw log files therefore
|
|
have the following structure.
|
|
|
|
```
|
|
"Start Application" --> Start Application
|
|
"Show Application"
|
|
"Transform start" --> Move
|
|
"Transform stop"
|
|
"Show Info" --> Flip Card
|
|
"Show Front"
|
|
"Artwork/OpenCard" --> Open Topic
|
|
"Artwork/CloseCard"
|
|
"ShowPopup" --> Open Popup
|
|
"HidePopup"
|
|
```
|
|
|
|
The right side shows what events can be extracted from these raw lines. The
|
|
"Start Application" is not an event in the original sense since it only
|
|
indicates if the table was started or maybe reset itself. This is not an
|
|
interaction with the table and therefore not interesting in itself. All
|
|
"Start Application" and "Show Application" are therefore excluded from the
|
|
data when further processed and are only in the raw log files.
|
|
|
|
# Parsing the raw log files
|
|
|
|
The first step is to parse the raw log files that are stored by the
|
|
application as text files in a rather unstructured format to a format that
|
|
can be read by common statistics software packages. The data are therefore
|
|
transferred to a spread sheet format. The following section describes what
|
|
problems were encountered while doing this.
|
|
|
|
## Corrupt lines
|
|
|
|
When reading the files containing the raw logs into R, a warning appears
|
|
that says
|
|
|
|
```
|
|
Warning messages:
|
|
incomplete final line found on '2016/2016_11_18-11_31_0.log'
|
|
incomplete final line found on '2016/2016_11_18-11_38_30.log'
|
|
incomplete final line found on '2016/2016_11_18-11_40_36.log'
|
|
...
|
|
```
|
|
|
|
When you open these files, it looks like the last line contains some binary
|
|
content. It is unclear why and how this happens. So when reading the data,
|
|
these lines were removed. A warning will be given that indicates how many
|
|
files have been affected.
|
|
|
|
## Extracted variables from raw log files
|
|
|
|
The following variables (columns in the data frame) are extracted from the
|
|
raw log file:
|
|
|
|
* `fileId`: Containing the zero-left-padded file name of the raw log file
|
|
the data line has been extracted from
|
|
|
|
* `folder`: The folder names in which the raw log files haven been
|
|
organized in. For the HAUM data set, the data are sorted by year (folders
|
|
2016, 2017, 2018, 2019, 2020, 2021, 2022, and 2023).
|
|
|
|
* `date`: Extracted timestamp from the raw log file in the format
|
|
`yyyy-mm-dd hh:mm:ss`.
|
|
|
|
* `timeMs`: Containing a timestamp in Milliseconds that restarts with
|
|
every new raw log files.
|
|
|
|
* `event`: Start and stop event tags. See above for possible values.
|
|
|
|
* `item`: Identifier of the different items. This is a three-digit
|
|
(left-padded) number. The numbers of the items correspond to the
|
|
folder names in `/ContentEyevisit/eyevisit_cards_light/` and were
|
|
orginally taken from the museums catalogue.
|
|
|
|
* `popup`: Name of the pop-up opened. This is only interesting for
|
|
"openPopup" events.
|
|
|
|
* `topic`: The number of the topic card that has been opened at the back of
|
|
the item card. See below for a more detailed descripttion what these
|
|
numbers mean.
|
|
|
|
* `x`: Value of x-coordinate in pixel on the 4K-Display ($3840 \times 2160$)
|
|
|
|
* `y`: Value of y-coordinate in pixel
|
|
|
|
* `scale`: Number in 128 bit that indicates how much the card has been
|
|
scaled
|
|
|
|
* `rotation`: Degree of rotation in start configuration.
|
|
|
|
<!-- TODO: Nach welchem Zeitintervall resettet sich der Tisch wieder in die
|
|
Ausgangskonfiguration? -> PM needs to look it up -->
|
|
|
|
## Variables after "closing of events"
|
|
|
|
The raw log data consist of start and stop events for each event type.
|
|
After preprocessing four event types are extracted: `move`, `flipCard`,
|
|
`openTopic`, and `openPopup`. Except for the `move` events, which can occur
|
|
at any time when interacting with an item card on the table, the events
|
|
have a hierarchical order: An item card first needs to be flipped
|
|
(`flipCard`), then the topic cards on the back of the card can be opened
|
|
(`openTopic`), and finally pop-ups on these topic cards can be opened
|
|
(`openPopup`). This implies that the event `openPopup` can only be present
|
|
for a certain item, if the card has already been flipped (i.e., an event
|
|
`flipCard` for the same item has already occured).
|
|
|
|
After preprocessing, the data frame is now in a wide format with columns
|
|
for the start and the stop of each event and contains the following
|
|
variables:
|
|
|
|
* `fileId.start` / `fileId.stop`: See above.
|
|
|
|
* `date.start` / `date.stop`: See above.
|
|
|
|
* `folder`: Containing the folder name (see above)
|
|
|
|
* `case`: A numerical variable indicating cases in the data. A "case"
|
|
indicates an interaction interval and could be defined in different ways.
|
|
Right now a new case begins, when no event occurred for 20 seconds or
|
|
longer.
|
|
|
|
* `path`: A path is defined as one interaction with one item A path
|
|
can either start with a `flipCard` event or when an item has been
|
|
touched for the first time within this case. A path ends with the
|
|
item card being flipped close again or with the last movement of the
|
|
card within this case. One case can contain several paths with the same
|
|
item when the item is flipped open and flipped close again several
|
|
times within a short time.
|
|
|
|
* `glossar`: An indicator variable with values 0/1 that tracks if a pop-up
|
|
has been opened from the glossar folder. These pop-ups can be assigned to
|
|
the wrong item since it is not possible to do this algorithmically.
|
|
It is possible that two items are flipped open that could both link to
|
|
the same pop-up from a glossar. The indicator variable is left as a
|
|
variable, so that these pop-ups can be easily deleted from the data.
|
|
Right now, glossar entries can be ignored completely by setting an
|
|
argument and this is done by default. Using the pop-ups from the glossar
|
|
will need a lot more love, before it behaves satisfactorily.
|
|
|
|
* `event`: Indicating the event. Can take tha values `move`, `flipCard`,
|
|
`openTopic`, and `openPopup`.
|
|
|
|
* `item`: Identifier of the different artworks and information cards. This
|
|
is a three-digit (left-padded) number. See above.
|
|
|
|
* `timeMs.start` / `timeMs.stop`: See above.
|
|
|
|
* `duration`: Calculated by $timeMs.stop - timeMs.start$ in Milliseconds.
|
|
Needs to be adjusted for events spanning more than one log file by a
|
|
factor of $60,000 \times \text{number of logfiles}$. See below for details.
|
|
|
|
* `topic`: See above.
|
|
|
|
* `popup`: See above.
|
|
|
|
* `x.start` / `x.stop`: See above.
|
|
|
|
* `y.start` / `y.stop`: See above.
|
|
|
|
* `distance`: Euclidean distande calculated from $(x.start, y.start)$ and
|
|
$(x.stop, y.stop)$.
|
|
|
|
* `scale.start` / `scale.stop`: See above.
|
|
|
|
* `scaleSize`: Relative scaling of item card, calculated by
|
|
$\frac{scale.stop}{scale.start}$.
|
|
|
|
* `rotation.start` / `rotation.stop`: See above.
|
|
|
|
* `rotationDegree`: Difference of rotation from $rotation.stop$ to
|
|
$rotation.start$.
|
|
|
|
## How unclosed events are handled
|
|
|
|
Events do not necessarily need to be completed. A person can, e.g., leave
|
|
the table and not flip the item card close again. For `flipCard`,
|
|
`openTopic`, and `openPopup` the data frame contains `NA` when the event
|
|
does not complete. For `move` events it happens quite often that a start
|
|
event follows a start event and a stop event follows a stop event.
|
|
Technically a move event cannot *not* be finished and the number of events
|
|
without a start or stop indicate that the time resolution was not
|
|
sufficient to catch all these events accurately. Double start and stop
|
|
`move` events have therefore been deleted from the data set.
|
|
|
|
## Additional meta data
|
|
|
|
For the HAUM data, I added meta data on state holidays and school
|
|
vacations.
|
|
|
|
This led to the following additional variables:
|
|
|
|
* `holiday`
|
|
|
|
* `vacations`
|
|
|
|
# Problems and how I handled them
|
|
|
|
This lists some problems with the log data that required decisions. These
|
|
decisions influence the outcome and maybe even the data quality. Hence, I
|
|
tried to document how I handled these problems and explain the decisions I
|
|
made.
|
|
|
|
## Weird behavior of `timeMs` and neg. `duration` values
|
|
|
|
`timeMs` resets itself every time a new log file starts. This means that
|
|
the durations of events spanning more than one log file must be adjusted.
|
|
Instead of just calculating $timeMs.stop - timeMs.start$, `timeMs.start`
|
|
must be subtracted from the maximum duration of the log file where the
|
|
event started ($600,000 ms$) and the `timeMs.stop` must be added. If the
|
|
event spans more than two log files, a multiple of $600,000$ must be taken,
|
|
e.g. for three log files it must be: $2 \times 600,000 - timeMs.start +
|
|
timeMs.stop$ and so on.
|
|
|
|
```{r timems, echo = FALSE, results = FALSE, fig.show = TRUE}
|
|
# Read data
|
|
datraw <- read.table("code/results/raw_logfiles_2024-02-21_16-07-33.csv", sep = ";",
|
|
header = TRUE)
|
|
|
|
plot(timeMs ~ as.factor(fileId), datraw[1:5000,], xlab = "fileId")
|
|
```
|
|
|
|
The boxplot shows that we have a continuous range of values within one log
|
|
file but that `timeMs` does not increase over log files. I kept
|
|
`timeMs.start` and `timeMs.stop` and also `fileId.start` and `fileId.stop`
|
|
in the data frame, so it is clear when events span more than one log file.
|
|
|
|
<!--
|
|
Infos from the programmer:
|
|
|
|
"Bin außerdem gerade den Code von damals durchgegangen. Das Logging läuft
|
|
so: Mit Start der Anwendung wird alle 10 Minuten ein neues Logfile
|
|
erstellt. Die Startzeit, von der aus die Duration berechnet wird, wird
|
|
jeweils neu gesetzt. Duration ist also nicht "Dauer seit Start der
|
|
Anwendung" sondern "Dauer seit Restart des Loggers". Deine Vermutung ist
|
|
also richtig - es sollte keine Durations >10 Minuten geben. Der erste
|
|
Eintrag eines Logfiles kann alles zwischen 0 und 10 Minuten sein (je
|
|
nachdem, ob der Tisch zum Zeitpunkt des neuen Logging-Intervalls in
|
|
Benutzung war). Wenn ein Case also über 2+ Logs verteilt ist, musst du auf
|
|
die Duration jeweils 10 Minuten pro Logfile nach dem ersten addieren, damit
|
|
es passt."
|
|
-->
|
|
|
|
## Left padding of file IDs
|
|
|
|
The file names of the raw log files are automatically generated and contain
|
|
a timestamp. This timestamp is not well formed. First, it contains an
|
|
incorrect month. The months go from 0 to 11 which means, that the file name
|
|
`2016_11_15-12_12_57.log` was collected on December 15, 2016 at 12:12 pm.
|
|
Another problem is that the file names are not zero left padded, e.g.,
|
|
`2016_11_15-12_2_57.log`. This file was collected on December 15, 2016 at
|
|
12:02 pm and therefore before the file above. But most sorting algorithms,
|
|
will sort these files in the order shown below. In order to preprocess the
|
|
data and close events that belong together, the data need to be sorted by
|
|
events and artworks repeatedly. In order to get them back in the correct
|
|
time order, it is necessary to order them based on three variables:
|
|
`fileId.start`, `date.start` and `timeMs.start`. The file IDs therefore
|
|
need to sort in the correct order (again see below for example). I zero
|
|
left padded the log file names within the data frame using it as an
|
|
identifier. These "file names" do not correspond exactly to the original
|
|
raw log file names. This needs to be kept in mind when doing any kind of
|
|
matching etc.
|
|
|
|
```
|
|
## what it looked like before left padding
|
|
# 1422 ../data/haum_logs_2016-2023/_2016b/2016_11_15-12_2_57.log 2016-12-15 12:12:56 599671 Transform start 076 076.xml NA 2092.25 2008.00 0.3000000 13.26874254
|
|
# 1423 ../data/haum_logs_2016-2023/_2016b/2016_11_15-12_12_57.log 2016-12-15 12:12:57 621 Transform start 076 076.xml NA 2092.25 2008.00 0.3000000 13.26523465
|
|
# 1424 ../data/haum_logs_2016-2023/_2016b/2016_11_15-12_12_57.log 2016-12-15 12:12:57 677 Transform stop 076 076.xml NA 2092.25 2008.00 0.2997736 13.26239605
|
|
# 1425 ../data/haum_logs_2016-2023/_2016b/2016_11_15-12_12_57.log 2016-12-15 12:12:57 774 Transform start 076 076.xml NA 2092.25 2008.00 0.2999345 13.26239605
|
|
# 1426 ../data/haum_logs_2016-2023/_2016b/2016_11_15-12_12_57.log 2016-12-15 12:12:57 850 Transform stop 076 076.xml NA 2092.25 2008.00 0.2997107 13.26223362
|
|
# 1427 ../data/haum_logs_2016-2023/_2016b/2016_11_15-12_2_57.log 2016-12-15 12:12:57 599916 Transform stop 076 076.xml NA 2092.25 2008.00 0.2997771 13.26523465
|
|
|
|
## what it looks like now
|
|
# 1422 2016_11_15-12_02_57.log 2016-12-15 12:12:56 599671 Transform start 076 076.xml NA 2092.25 2008.00 0.3000000 13.26874254
|
|
# 1423 2016_11_15-12_02_57.log 2016-12-15 12:12:57 599916 Transform stop 076 076.xml NA 2092.25 2008.00 0.2997771 13.26523465
|
|
# 1424 2016_11_15-12_12_57.log 2016-12-15 12:12:57 621 Transform start 076 076.xml NA 2092.25 2008.00 0.3000000 13.26523465
|
|
# 1425 2016_11_15-12_12_57.log 2016-12-15 12:12:57 677 Transform stop 076 076.xml NA 2092.25 2008.00 0.2997736 13.26239605
|
|
# 1426 2016_11_15-12_12_57.log 2016-12-15 12:12:57 774 Transform start 076 076.xml NA 2092.25 2008.00 0.2999345 13.26239605
|
|
# 1427 2016_11_15-12_12_57.log 2016-12-15 12:12:57 850 Transform stop 076 076.xml NA 2092.25 2008.00 0.2997107 13.26223362
|
|
```
|
|
|
|
## Timestamps repeat
|
|
|
|
The timestamps in the `date` variable record year, month, day, hour,
|
|
minute and seconds. Since one second is not a very short time interval for
|
|
a move on a touch display, this is not fine grained enough to bring events
|
|
into the correct order, meaning there are events from the same log file
|
|
having the same timestamp and even events from different log files having
|
|
the same timestamp. The log files get written about every 10 minutes
|
|
(which can easily be seen when looking at the file names of the raw log
|
|
files). So in order to get events in the correct order, it is necessary to
|
|
first order by file ID, within file ID then sort by timestamp `date` and
|
|
then within these more coarse grained timestamps sort be `timeMs`. But as
|
|
explained above, `timeMs` can only be sorted within one file ID, since they
|
|
do not increase consistently over log files, but have a new setoff for each
|
|
raw log file.
|
|
|
|
## x,y-coordinates outside of display range
|
|
|
|
The display of the Multi-Touch-Table is a 4K-display with 3840 x 2160
|
|
pixels. When you plot the start and stop coordinates, the display is
|
|
clearly distinguishable. However, a lot of points are outside of the
|
|
display range. This can happen, when the art objects are scaled and then
|
|
moved to the very edge of the table. Then it will record pixels outside of
|
|
the table. These are actually valid data points and I will leave them as
|
|
is.
|
|
|
|
```{r xycoord}
|
|
datlogs <- read.table("code/results/event_logfiles_2024-02-21_16-07-33.csv", sep = ";",
|
|
header = TRUE)
|
|
|
|
par(mfrow = c(1, 2))
|
|
plot(y.start ~ x.start, datlogs)
|
|
abline(v = c(0, 3840), h = c(0, 2160), col = "blue", lwd = 2)
|
|
plot(y.stop ~ x.stop, datlogs)
|
|
abline(v = c(0, 3840), h = c(0, 2160), col = "blue", lwd = 2)
|
|
|
|
aggregate(cbind(x.start, x.stop, y.start, y.stop) ~ 1, datlogs, mean)
|
|
```
|
|
|
|
## Pop-ups from glossar cannot be assigned to a specific item
|
|
|
|
All the information, pictures and texts for the topics and pop-ups are
|
|
stored in `/data/haum/ContentEyevisit/eyevisit_cards_light/<item_number>`.
|
|
Among other things, each folder contains XML-files with the information
|
|
about any technical terms that can be opened from the hypertexts on the
|
|
topic cards. Often these information are item dependent and then the
|
|
corresponding XML-file is in the folder for this item. Sometimes, however,
|
|
more general terms can be opened. In order to avoid multiple files
|
|
containing the same information, these were stored in a folder called
|
|
`glossar` and get accessed from there. The raw log files only contain the
|
|
path to this glossar entry and did not record from which item it was
|
|
accessed. I tried to assign these glossar entries to the correct items. The
|
|
(very heuristic) approach was this:
|
|
|
|
1. Create a lookup table with all XML-file names (possible pop-ups) from
|
|
the glossar folder and what items possibly call them. This was stored
|
|
as an `RData` object for easier handling but should maybe be stored in a
|
|
more interoperable format.
|
|
|
|
2. I went through all possible pop-ups in this lookup table and stored the
|
|
items that are associated with it.
|
|
|
|
3. I created a sub data frame without move events (since they can never be
|
|
associated with a pop-up) and went through every line and looked up if
|
|
an item and a topic card had been opened. If this was the case and a
|
|
glossar entry came up before the item was closed again, I assigned
|
|
this item to the glossar entry.
|
|
|
|
This is heuristic since it is possible that several topic cards from
|
|
different items are opened simultaneously and the glossar pop-up could
|
|
be opened from either one (it could even be more than two, of course). In
|
|
these cases the item that was opened closest to the glossar pop-up has
|
|
been assigned, but this can never be completely error free.
|
|
|
|
And this heuristic only assigns a little more than half of the glossar
|
|
entries. Since my heuristic only looks for the last item that has been
|
|
opened and if this item is a possible candidate it misses all glossar
|
|
pop-ups where another item has been opened in between. This is still an
|
|
open TODO to write a more elaborate algorithm.
|
|
|
|
All glossar pop-ups that do not get matched with an item are removed
|
|
from the data set with a warning if the argument `glossar = TRUE` is set.
|
|
Otherwise the glossar entries will be ignored completely.
|
|
|
|
## Assign a `case` variable based on "time heuristic"
|
|
|
|
One thing needed in order to work with the data set and use it for machine
|
|
learning algorithms like process mining, is a variable that tries to
|
|
identify a case. A case variable will structure the data frame in a way
|
|
that navigation behavior can actually be investigated. However, we do not
|
|
know if several people are standing around the table interacting with it or
|
|
just one very active person. The simplest way to define a case variable is
|
|
to just use a time limit between events. This means that when the table has
|
|
not been interacted with for, e.g., 20 seconds than it is assumed that a
|
|
person moved on and a new person started interacting with the table. This
|
|
is the easiest heuristic and implemented at the moment. Process mining
|
|
shows that this simple approach works in a way that the correct process
|
|
gets extracted by the algorithm.
|
|
|
|
In order to investigate user behavior on a more fine grained level, it will
|
|
be necessary to come up with a more elaborate approach. A better, still
|
|
simple approach, could be to use this kind of time limit and additionally
|
|
look at the distance between items interacted with within one time window.
|
|
When items are far apart it seems plausible that more than one person
|
|
interacted with them. Very short time lapses between events on different
|
|
items could also be an indicator that more than one person is interacting
|
|
with the table.
|
|
|
|
## Assign a `path` variable
|
|
|
|
The `path` variable is supposed to show one interaction trace with one
|
|
artwork. Meaning it starts when an artwork is touched or flipped and stops
|
|
when it is closed again. It is easy to assign a path from flipping a card
|
|
over opening (maybe several) topics and pop-ups for this artwork card until
|
|
closing this card again. But one would like to assign the same path to
|
|
move events surrounding this interaction. Again, this is not possible in an
|
|
algorithmic way but only heuristically.
|
|
|
|
Again, I used a time cutoff for this. First, if a `move` event occurs, it
|
|
is checked, if the same item has been flipped less than 20 seconds
|
|
beforehand. If yes, the same path indicator is assigned to this `move`. If
|
|
not, temporarily a new "move indicator" is assigned. Then, a "backward
|
|
pass" is applied, where it is checked if the same item is opened less than
|
|
20 seconds _after_ the event occurs. If yes, that path indicator is
|
|
assigned. For all the remaining moves, a new path number is assigned. This
|
|
corresponds to items being moved without being flipped.
|
|
|
|
## A `move` event does not record any change
|
|
|
|
Most of the events in the log files are move events. Additionally, many of
|
|
these move events are recorded but they do not indicate any change, meaning
|
|
the only difference is the timestamp. All other variables indicating moves
|
|
like `x.start` and `x.stop`, `rotation.start` and `rotation.stop` etc. do
|
|
not show _any_ change. They represent about 2/3 of all move events. These
|
|
events are probably short touches of the table without an actual
|
|
interaction. They were therefore removed from the data set.
|
|
|
|
## Card indices go from 0 to 7 (instead of 0 to 5 as expected)
|
|
|
|
In the beginning I thought that the number for topics was the index of
|
|
where the card was presented on the back of the item. But this is not
|
|
correct. It is the number of the topic. There are eight topics in total:
|
|
|
|
```
|
|
Indices for topics:
|
|
0 artist
|
|
1 thema
|
|
2 komposition
|
|
3 leben des kunstwerks
|
|
4 details
|
|
5 licht und farbe
|
|
6 extra info
|
|
7 technik
|
|
```
|
|
On the back of items, there can be between 2 to 6 topic cards. Several of
|
|
these topic cards can be about the same topic, e.g., there can be two topic
|
|
cards assigned to the topic `thema`. It is impossible to find out if the
|
|
same topic card was opened several times or if different topic cards with
|
|
the same topic were opened from the same item. See example below for item
|
|
"001".
|
|
|
|
```{r topics, echo = FALSE}
|
|
items <- sprintf("%03d", unique(datlogs$item))
|
|
topics <- extract_topics(items, xmlfiles = paste0(items, ".xml"),
|
|
xmlpath = "data/haum/ContentEyevisit/eyevisit_cards_light/")
|
|
head(topics)
|
|
```
|
|
|
|
## New artworks "504" and "505" starting October 2022
|
|
|
|
When I read in the complete data frame for the first time, all of the
|
|
sudden there were 72 instead of 70 items. It seems like these two
|
|
artworks appear on October 21, 2022.
|
|
|
|
```{r newitems}
|
|
summary(as.Date(datraw[datraw$item %in% c("504", "505"), "date"]))
|
|
```
|
|
|
|
The artworks seem to be have updated in general after October 21, 2022. The
|
|
following table shows which items were presented in which years.
|
|
|
|
```{r years}
|
|
xtabs(~ item + lubridate::year(date.start), datlogs)
|
|
```
|
|
|
|
It shows that the artworks haven been updated after the Corona pandemic. I
|
|
think, the table was also moved to a different location at that point.
|
|
|