Dit zijn de resultaten van de Multicriteria Decision Analysis die het publiek van de “Het Doet Je Wat! Live.”-avond in de Werkspoorkathedraal in Utrecht op vrijdag 6 november 2026.
Dit bestand staat op https://mcda.mdmabeleid.nl/, en het bijbehorende zogenaamde “Git repository” staat op https://codeberg.org/nightlife/nano-mcda-het-doet-je-wat.
De slides die hierbij horen staan op https://doi.org/rv6z, en de bijbehorende blog post met alle links staat op https://edu.nl/fgnv9.
Als je dit interessant vond, vind je de filmpjes die ik heb opgenomen met de Universiteit van Nederland misschien ook interessant. Daar link ik naar vanaf deze blog post.
### "Slugs": parts of variable names used for easy selection
criteriaSlugs <- c("damageSelf", "damageOther",
"benefits", "addiction");
names(criteriaSlugs) <- criteriaSlugs;
drugSlugs <- c("Alcohol", "Ecstasy", "Nicotine",
"Mushrooms", "3MMC");
names(drugSlugs) <- drugSlugs;
criteriaSlugs_recoded <- paste0(criteriaSlugs, "_recoded");
names(criteriaSlugs_recoded) <- criteriaSlugs_recoded;
drugSlugs_recoded <- paste0(drugSlugs, "_recoded");
names(drugSlugs_recoded) <- drugSlugs_recoded;
titles <-
list(criterion =
list(damageSelf = "Schade gebruiker",
damageOther = "Schade anderen",
benefits = "Voordelen",
addiction = "Verslavingsrisico"),
drug =
list(
"Alcohol" = "Alcohol 🍻",
"Ecstasy" = "Ecstasy 💊",
"Nicotine" = "Nicotine 🚬",
"Mushrooms" = "Paddo's 🍄",
"3MMC" = "3MMC 🍚"
)
);
originalDrugOrder <- unlist(titles$drug);
weightSigns <-
list(
damageSelf = 1,
damageOther = 1,
benefits = -1,
addiction = 1
);
estimateVarNames <-
apply(
expand.grid(criteriaSlugs, drugSlugs),
1,
paste,
collapse="_"
);
ratingsToScores <- function(ratings,
weights,
weightOrder = names(weightSigns)) {
return(mean(ratings * (unlist(weightSigns[weightOrder]) * weights)));
}
### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
### Import data
### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
dat.raw <-
read.csv(here::here("Het-doet-je-wat!-Live..csv"));
dat <- dat.raw;
### Remove test entries
dat <- dat[3:nrow(dat), ];
### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
### Rename variables
### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
names(dat) <- sub("Paddo.s",
"Mushrooms",
names(dat), fixed = TRUE);
names(dat) <- sub("Oefenvraag..hoe.lekker.vindt.je.deze.dingen.......",
"oefenvraag_",
names(dat), fixed = TRUE);
names(dat) <- sub("Risico.s.en.schade.voor.de.gebruiker.......",
"damageSelf_",
names(dat), fixed = TRUE);
names(dat) <- sub("Risico.s.en.schade.voor.andere.mensen.......",
"damageOther_",
names(dat), fixed = TRUE);
names(dat) <- sub("Voordelen.voor.de.gebruiker.......",
"benefits_",
names(dat), fixed = TRUE);
names(dat) <- sub("Verslavingsrisico.......",
"addiction_",
names(dat), fixed = TRUE);
names(dat) <- sub("Weging....",
"weights_",
names(dat), fixed = TRUE);
names(dat)[names(dat) == "weights_Schade"] <- "weights_damageSelf";
names(dat)[names(dat) == "weights_Schade.anderen"] <- "weights_damageOther";
names(dat)[names(dat) == "weights_Verslaving"] <- "weights_addiction";
names(dat)[names(dat) == "weights_Voordelen"] <- "weights_benefits";
names(dat)[names(dat) == "Je.unieke.code"] <- "uniqueCode";
### Rescale scores in case the highest score was not 5 and/or the lowest
### score was not 1
### Also set weights on a scale from 1 to 5
for (i in 1:nrow(dat)) {
for (criterion in criteriaSlugs) {
criterionMax <-
max(as.numeric(dat[i, paste0(criterion, "_", drugSlugs)]));
criterionMin <-
min(as.numeric(dat[i, paste0(criterion, "_", drugSlugs)]));
dat[i, paste0(criterion, "_", drugSlugs_recoded)] <-
(dat[i, paste0(criterion, "_", drugSlugs)] - criterionMin) /
(criterionMax - criterionMin);
dat[i, paste0(criterion, "_", drugSlugs_recoded)] <-
(4 * dat[i, paste0(criterion, "_", drugSlugs_recoded)]) + 1;
}
}
### Rescale weights in case the highest weight was not 100;
### Also set weights on a scale from 0 to 1
for (i in 1:nrow(dat)) {
### Remove percentage symbol
dat[i, paste0("weights_", criteriaSlugs)] <-
sub("%", "", trimws(dat[i, paste0("weights_", criteriaSlugs)]),
fixed = TRUE);
### Compute multiplier based on highest weight
multiplier <-
1 / max(as.numeric(dat[i, paste0("weights_", criteriaSlugs)]));
### Perform correction
dat[i, paste0("correctedWeights_", criteriaSlugs)] <-
multiplier * as.numeric(dat[i, paste0("weights_", criteriaSlugs)]);
}
ratingMeans <-
unlist(
lapply(
criteriaSlugs,
function(criterion) {
return(
lapply(
drugSlugs_recoded,
function(drug) {
return(
mean(
dat[, paste(criterion,
drug,
sep = "_")],
na.rm = TRUE
)
);
}
)
);
}
),
recursive = FALSE
);
ratingMeansDf <-
data.frame(
rating = unlist(ratingMeans)
);
ratingMeansDf$criterion =
factor(unlist(lapply(strsplit(names(ratingMeans), "\\."), `[[`, 1)),
levels = unique(unlist(lapply(strsplit(names(ratingMeans), "\\."), `[[`, 1))),
ordered = TRUE);
ratingMeansDf$drug =
factor(unlist(lapply(strsplit(names(ratingMeans), "\\."), `[[`, 2)),
levels = unique(unlist(lapply(strsplit(names(ratingMeans), "\\."), `[[`, 2))),
ordered = TRUE);
ratingMeansDf$drug <- sub("_recoded", "", ratingMeansDf$drug, fixed = TRUE);
### Weights
meanWeights <-
colMeans(
dat[, grep("correctedWeights_", names(dat), value=TRUE)]
);
meanWeights_rescaled <- (100 * max(meanWeights)) * meanWeights;
names(meanWeights) <-
names(titles$criterion);
### Add to rating means
ratingMeansDf$weight <-
meanWeights_rescaled[ratingMeansDf$criterion];
ratingMeansDf$weightedRating_unsigned <-
ratingMeansDf$rating * ratingMeansDf$weight;
ratingMeansDf$weightedRating <-
unlist(weightSigns[ratingMeansDf$criterion]) * ratingMeansDf$weightedRating_unsigned;
meanScores <- list();
for (drug in names(titles$drug)) {
meanScores[[drug]] <-
ratingsToScores(
ratingMeansDf[ratingMeansDf$drug == drug, 'rating'],
meanWeights_rescaled * 2
#weightOrder = 1
);
}
meanScoresDf <-
data.frame(
drug = names(titles$drug),
score = unlist(meanScores)
);
Deze secties laten de frequenties en gemiddelden zien van de schattingen (voor de weging).
De tweede tab bevat de frequenties waarmnee elke schatting voorkwam als nummer.
ratingFreqs <-
unlist(
lapply(
criteriaSlugs,
function(criterion) {
return(
lapply(
drugSlugs_recoded,
function(drug) {
return(
table(
dat[, paste(criterion,
drug,
sep = "_")]
)
);
}
)
);
}
),
recursive = FALSE
);
ratingPlots <-
unlist(
lapply(
criteriaSlugs,
function(criterion) {
return(
lapply(
drugSlugs,
function(drug) {
res <-
ggplot2::ggplot(
dat,
mapping = ggplot2::aes(
x = .data[[paste(criterion,
drug,
sep = "_")]]
)
) +
ggplot2::geom_bar(na.rm = TRUE) +
ggplot2::coord_cartesian(
xlim = c(1, 5),
ylim = c(0, max(unlist(ratingFreqs)))
) +
ggplot2::scale_x_continuous(
breaks = 1:5
) +
ggplot2::labs(
x = NULL,
y = NULL,
title =
paste0(
titles$drug[[drug]],
", ",
titles$criterion[[criterion]]
)
) +
ggplot2::theme_minimal();
if (criterion != criteriaSlugs[1]) {
res <- res +
ggplot2::theme(
axis.title.y = ggplot2::element_blank(),
axis.text.y = ggplot2::element_blank(),
axis.ticks.y = ggplot2::element_blank()
);
}
if (drug != drugSlugs[5]) {
res <- res +
ggplot2::theme(
axis.title.x = ggplot2::element_blank(),
axis.text.x = ggplot2::element_blank(),
axis.ticks.x = ggplot2::element_blank()
);
}
return(res);
}
)
);
}
),
recursive = FALSE
);
patchwork::wrap_plots(
ratingPlots,
byrow = FALSE,
ncol = 4,
guides = "collect"
);
print(ratingFreqs);
## $damageSelf.Alcohol_recoded
##
## 1 2 2.33333333333333 3
## 2 1 1 3
## 3.66666666666667 4 5
## 7 13 53
##
## $damageSelf.Ecstasy_recoded
##
## 1 2 2.33333333333333 3
## 43 18 5 11
## 3.66666666666667 4 5
## 1 1 1
##
## $damageSelf.Nicotine_recoded
##
## 1 2 2.33333333333333 3
## 9 1 1 6
## 3.66666666666667 4 5
## 6 14 43
##
## $damageSelf.Mushrooms_recoded
##
## 1 2 2.33333333333333 3
## 52 8 5 8
## 3.66666666666667 5
## 2 5
##
## $damageSelf.3MMC_recoded
##
## 1 2 2.33333333333333 3
## 11 2 6 23
## 3.66666666666667 4 5
## 8 11 19
##
## $damageOther.Alcohol_recoded
##
## 1 2 2.33333333333333 3
## 5 2 3 2
## 3.66666666666667 4 5
## 3 3 65
##
## $damageOther.Ecstasy_recoded
##
## 1 2 2.33333333333333 3
## 68 5 1 5
## 3.66666666666667 4 5
## 2 1 1
##
## $damageOther.Nicotine_recoded
##
## 1 2 2.33333333333333 3
## 13 4 7 6
## 3.66666666666667 4 5
## 5 15 33
##
## $damageOther.Mushrooms_recoded
##
## 1 2 2.33333333333333 3
## 49 12 6 7
## 3.66666666666667 4 5
## 2 4 3
##
## $damageOther.3MMC_recoded
##
## 1 2 2.33333333333333 3
## 26 14 11 15
## 3.66666666666667 4 5
## 6 5 6
##
## $benefits.Alcohol_recoded
##
## 1 2 2.33333333333333 3
## 23 7 9 18
## 3.66666666666667 4 5
## 8 6 8
##
## $benefits.Ecstasy_recoded
##
## 1 3 3.66666666666667 4
## 2 3 6 10
## 5
## 58
##
## $benefits.Nicotine_recoded
##
## 1 2 2.33333333333333 3
## 58 10 1 1
## 3.66666666666667 4 5
## 3 2 4
##
## $benefits.Mushrooms_recoded
##
## 1 2 2.33333333333333 3
## 10 4 5 18
## 3.66666666666667 4 5
## 5 8 29
##
## $benefits.3MMC_recoded
##
## 1 2 2.33333333333333 3
## 21 4 6 20
## 3.66666666666667 4 5
## 4 8 16
##
## $addiction.Alcohol_recoded
##
## 2.33333333333333 3 3.66666666666667 4
## 2 4 6 20
## 5
## 50
##
## $addiction.Ecstasy_recoded
##
## 1 2 2.33333333333333 3
## 49 16 2 9
## 3.66666666666667 5
## 1 5
##
## $addiction.Nicotine_recoded
##
## 3 4 5
## 4 6 72
##
## $addiction.Mushrooms_recoded
##
## 1 2 2.33333333333333 3
## 62 10 4 5
## 5
## 1
##
## $addiction.3MMC_recoded
##
## 1 2 2.33333333333333 3
## 14 7 11 21
## 4 5
## 8 21
Dit zijn de schattingen gemiddeld over de deelnemers.
ratingMeansDf$drug_factor <-
factor(ratingMeansDf$drug,
levels = names(originalDrugOrder),
labels = originalDrugOrder,
ordered = TRUE);
ratingMeansPlot <-
ggplot2::ggplot(
data = ratingMeansDf,
mapping =
ggplot2::aes(
x = criterion,
y = rating,
group = drug_factor,
color = drug_factor,
fill = drug_factor
)
) +
ggplot2::geom_line(linewidth = 1) +
ggplot2::coord_cartesian(
ylim = c(1, 5)
) +
ggplot2::scale_y_continuous(
breaks = 1:5
) +
ggplot2::scale_color_discrete(
palette = c("#fa3e8e", "#f2c616", "#fdb3c8", "#f1e3ab", "#ffffff")
) +
ggplot2::theme_minimal(
ink = "white",
accent = "#ccc",
paper = "transparent"
) +
ggplot2::labs(
y = "Schatting",
x = "Drug",
color = "Drug"
);
ggplot2::ggsave(
here::here("output", "rating-means-unweighted.png"),
ratingMeansPlot,
width = 9,
height = 5,
dpi = 300,
bg = NA
);
ratingMeansPlot +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "#664a9d"
);
knitr::kable(meanScoresDf);
| drug | score | |
|---|---|---|
| Alcohol | Alcohol | 401.96640 |
| Ecstasy | Ecstasy | 30.92750 |
| Nicotine | Nicotine | 402.00911 |
| Mushrooms | Mushrooms | 64.04024 |
| 3MMC | 3MMC | 227.22597 |
meanScoresDf_clean <-
meanScoresDf;
meanScoresDf_clean$drug <-
as.character(titles$drug[meanScoresDf_clean$drug]);
meanScoresDf_clean$drug_factor <-
factor(meanScoresDf_clean$drug,
levels = originalDrugOrder,
labels = originalDrugOrder,
ordered = TRUE);
aggregatedScores <-
ggplot2::ggplot(
data = meanScoresDf_clean,
mapping = ggplot2::aes(
x = drug_factor,
y = score,
fill = drug
)
) +
ggplot2::geom_col() +
#ggplot2::scale_fill_viridis_d() +
ggplot2::scale_fill_discrete(
palette = c("#fa3e8e", "#f2c616", "#fdb3c8", "#f1e3ab", "#ffffff")
) +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "transparent"
) +
ggplot2::labs(
y = "Score",
x = "Drug",
fill = "Drug"
);
ggplot2::ggsave(
here::here("output", "aggregated-scores.png"),
aggregatedScores,
width = 9,
height = 5,
dpi = 300,
bg = NA
);
aggregatedScores +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "#664a9d"
);
### Ordered by drug
decreasingDrugOrder <- order(meanScoresDf_clean$score, decreasing = TRUE);
orderedDrugLabels <- meanScoresDf_clean$drug[order(meanScoresDf_clean$score, decreasing = TRUE)];
meanScoresDf_clean$drug_factor_ordered <-
factor(meanScoresDf_clean$drug,
levels = orderedDrugLabels,
labels = orderedDrugLabels,
ordered = TRUE);
aggregatedScores_ordered <-
ggplot2::ggplot(
data = meanScoresDf_clean,
mapping = ggplot2::aes(
x = drug_factor_ordered,
y = score,
fill = drug
)
) +
ggplot2::geom_col() +
#ggplot2::scale_fill_viridis_d() +
ggplot2::scale_fill_discrete(
palette = c("#fa3e8e", "#f2c616", "#fdb3c8", "#f1e3ab", "#ffffff")
) +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "transparent"
) +
ggplot2::labs(
y = "Score",
x = "Drug",
fill = "Drug"
);
ggplot2::ggsave(
here::here("output", "aggregated-scores-ordered.png"),
aggregatedScores_ordered,
width = 9,
height = 5,
dpi = 300,
bg = NA
);
aggregatedScores_ordered +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "#664a9d"
);
knitr::kable(ratingMeansDf);
| rating | criterion | drug | weight | weightedRating_unsigned | weightedRating | drug_factor | |
|---|---|---|---|---|---|---|---|
| damageSelf.Alcohol_recoded | 4.475000 | damageSelf | Alcohol | 71.48920 | 319.91416 | 319.91416 | Alcohol 🍻 |
| damageSelf.Ecstasy_recoded | 1.704167 | damageSelf | Ecstasy | 71.48920 | 121.82951 | 121.82951 | Ecstasy 💊 |
| damageSelf.Nicotine_recoded | 4.054167 | damageSelf | Nicotine | 71.48920 | 289.82912 | 289.82912 | Nicotine 🚬 |
| damageSelf.Mushrooms_recoded | 1.700000 | damageSelf | Mushrooms | 71.48920 | 121.53164 | 121.53164 | Paddo’s 🍄 |
| damageSelf.3MMC_recoded | 3.329167 | damageSelf | 3MMC | 71.48920 | 237.99945 | 237.99945 | 3MMC 🍚 |
| damageOther.Alcohol_recoded | 4.457831 | damageOther | Alcohol | 68.82874 | 306.82689 | 306.82689 | Alcohol 🍻 |
| damageOther.Ecstasy_recoded | 1.345382 | damageOther | Ecstasy | 68.82874 | 92.60091 | 92.60091 | Ecstasy 💊 |
| damageOther.Nicotine_recoded | 3.598394 | damageOther | Nicotine | 68.82874 | 247.67288 | 247.67288 | Nicotine 🚬 |
| damageOther.Mushrooms_recoded | 1.763052 | damageOther | Mushrooms | 68.82874 | 121.34865 | 121.34865 | Paddo’s 🍄 |
| damageOther.3MMC_recoded | 2.369478 | damageOther | 3MMC | 68.82874 | 163.08817 | 163.08817 | 3MMC 🍚 |
| benefits.Alcohol_recoded | 2.599156 | benefits | Alcohol | 61.27645 | 159.26707 | -159.26707 | Alcohol 🍻 |
| benefits.Ecstasy_recoded | 4.594937 | benefits | Ecstasy | 61.27645 | 281.56143 | -281.56143 | Ecstasy 💊 |
| benefits.Nicotine_recoded | 1.548523 | benefits | Nicotine | 61.27645 | 94.88801 | -94.88801 | Nicotine 🚬 |
| benefits.Mushrooms_recoded | 3.531646 | benefits | Mushrooms | 61.27645 | 216.40672 | -216.40672 | Paddo’s 🍄 |
| benefits.3MMC_recoded | 2.907173 | benefits | 3MMC | 61.27645 | 178.14126 | -178.14126 | 3MMC 🍚 |
| addiction.Alcohol_recoded | 4.495935 | addiction | Alcohol | 74.83623 | 336.45883 | 336.45883 | Alcohol 🍻 |
| addiction.Ecstasy_recoded | 1.723577 | addiction | Ecstasy | 74.83623 | 128.98602 | 128.98602 | Ecstasy 💊 |
| addiction.Nicotine_recoded | 4.829268 | addiction | Nicotine | 74.83623 | 361.40424 | 361.40424 | Nicotine 🚬 |
| addiction.Mushrooms_recoded | 1.357724 | addiction | Mushrooms | 74.83623 | 101.60692 | 101.60692 | Paddo’s 🍄 |
| addiction.3MMC_recoded | 3.093496 | addiction | 3MMC | 74.83623 | 231.50558 | 231.50558 | 3MMC 🍚 |
ratingMeansDf_clean <- ratingMeansDf;
ratingMeansDf_clean$drug <-
as.character(titles$drug[ratingMeansDf_clean$drug]);
ratingMeansDf_clean$criterion <-
as.character(titles$criterion[ratingMeansDf_clean$criterion]);
ratingMeansDf_clean$drug_factor <-
factor(ratingMeansDf_clean$drug,
levels = originalDrugOrder,
labels = originalDrugOrder,
ordered = TRUE);
aggregatedScores_per_criterion <-
ggplot2::ggplot(
data = ratingMeansDf_clean,
mapping = ggplot2::aes(
x = drug_factor,
y = weightedRating,
fill = criterion,
group = criterion
)
) +
ggplot2::geom_col() +
#ggplot2::scale_fill_viridis_d() +
ggplot2::scale_fill_discrete(
palette = c("#fa3e8e", "#f2c616", "#fdb3c8", "#f1e3ab", "#ffffff")
) +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "transparent"
) +
ggplot2::labs(
y = "Gewogen scores",
x = "Drug",
fill = "Criterium"
);
ggplot2::ggsave(
here::here("output", "aggregated-scores-per-criterion.png"),
aggregatedScores_per_criterion,
width = 9,
height = 5,
dpi = 300,
bg = "transparent"
);
aggregatedScores_per_criterion +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "#664a9d"
);
### Ordered by drug
ratingMeansDf_clean$drug_factor_ordered <-
factor(ratingMeansDf_clean$drug,
levels = orderedDrugLabels,
labels = orderedDrugLabels,
ordered = TRUE);
aggregatedScores_per_criterion_ordered <-
ggplot2::ggplot(
data = ratingMeansDf_clean,
mapping = ggplot2::aes(
x = drug_factor_ordered,
y = weightedRating,
fill = criterion,
group = criterion
)
) +
ggplot2::geom_col() +
#ggplot2::scale_fill_viridis_d() +
ggplot2::scale_fill_discrete(
palette = c("#fa3e8e", "#f2c616", "#fdb3c8", "#f1e3ab", "#ffffff")
) +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "transparent"
) +
ggplot2::labs(
y = "Gewogen scores",
x = "Drug",
fill = "Criterium"
);
ggplot2::ggsave(
here::here("output", "aggregated-scores-per-criterion-ordered.png"),
aggregatedScores_per_criterion_ordered,
width = 9,
height = 5,
dpi = 300,
bg = "transparent"
);
aggregatedScores_per_criterion_ordered +
ggplot2::theme_minimal(
base_size = 16,
ink = "white",
accent = "#ccc",
paper = "#664a9d"
);
Dit zijn de schattingen, wegingen, en scores per deelnemer. Als je nog weet welke unieke code je hebt ingevoerd, kun je in de andere tabs in deze sectie terugvinden wat je hebt ingevoerd, samen met de uiteindelijke scores die daaruit volgen en de bijbehorende datavisualisaties.