Author SHA1 Message Date
christian 62fe9d497d Merge branch 'master' into genderequality-alternative 2021-08-10 23:28:25 +02:00
christian 29a7974941 change title of vignettes, and add install instructions for vignettes 2021-08-10 22:47:07 +02:00
christian e31ccabf18 change title in genderequality 2021-08-10 22:46:41 +02:00
christian 622fd4db07 Merge branch 'master' into genderequality-alternative 2021-08-10 22:03:37 +02:00
christian 6aa80534f8 add checks to read/write csv and refactor 2021-08-10 21:51:00 +02:00
christian 605e5e976a Merge branch 'master' into genderequality-alternative 2021-08-10 21:27:10 +02:00
christian 8e691e5d11 update package meta data 2021-08-10 21:24:39 +02:00
christian 6b1f8a64b2 add installation directives 2021-08-10 21:14:52 +02:00
christian 7e304d12bb fix formatting in readme and improve analysis section 2021-08-10 20:58:29 +02:00
christian 864c0016cc update readme 2021-08-10 20:54:37 +02:00
christian 489254dadf Merge branch 'master' into genderequality-alternative 2021-08-10 20:19:02 +02:00
christian b09742815a Merge branch 'master' into genderequality-alternative 2021-08-10 17:54:04 +02:00
christian 7daf9e553c Merge branch 'master' into genderequality-alternative 2021-08-10 17:34:29 +02:00
christian d657ca3fbe Merge branch 'master' into genderequality-alternative 2021-08-10 17:26:49 +02:00
christian c53d842a1e add missing function to genderequality 2021-08-10 17:26:05 +02:00
christian f753920d34 add masterdata from bundestag.de and use this for genderequality 2021-08-10 17:04:43 +02:00
11 changed files with 109230 additions and 165 deletions
+15 -7
View File
@@ -1,13 +1,21 @@
Package: hateimparlament Package: hateimparlament
Title: Protocolanalysis of German Bundestag Title: Recordanalysis Of Bundestag
Version: 0.0.0.9000 Version: 0.0.0.9000
Authors@R: Authors@R: c(
person(given = "First", person(given = "Leon",
family = "Last", family = "Burgard",
role = c("aut")),
person(given = "Josua",
family = "Kugler",
role = c("aut")),
person(given = "Christian",
family = "Merten",
role = c("aut", "cre"), role = c("aut", "cre"),
email = "first.last@example.com", email = "christian@merten.dev"))
comment = c(ORCID = "YOUR-ORCID-ID")) Description: Downloads, parses and analyses parliamentary records of the 19th legislative
Description: Downloads, parses and analyses protocols of the current German parliament (Bundestag). period of the German parliament (Bundestag).
URL: https://git.flavigny.de/christian/hateimparlament
BugReports: https://git.flavigny.de/christian/hateimparlament/issues
License: GPL (>= 3) License: GPL (>= 3)
Encoding: UTF-8 Encoding: UTF-8
LazyData: true LazyData: true
-7
View File
@@ -61,10 +61,3 @@ fetch_all <- function(download_dir="inst/records/", create=FALSE) {
# if successful, set progressbar to 100% # if successful, set progressbar to 100%
setTimerProgressBar(pb, 250) setTimerProgressBar(pb, 250)
} }
stop_dir_not_creatable <- function(cond) {
# currently this has call: dir.create(download_dir)
# do we want to change this to fetch_all(...) ?
cond$message <- "Directory does not exist and can't be created. Probably because the path is not writeable."
stop(cond)
}
+7
View File
@@ -19,6 +19,13 @@ check_directory <- function(path, create=F) {
} }
} }
stop_dir_not_creatable <- function(cond) {
# currently this has call: dir.create(download_dir)
# do we want to change this to fetch_all(...) ?
cond$message <- "Directory does not exist and can't be created. Probably because the path is not writeable."
stop(cond)
}
# appends a file seperator at end of path if needed # appends a file seperator at end of path if needed
make_directory_path <- function(path) { make_directory_path <- function(path) {
if (!str_ends(path, .Platform$file.sep)) str_c(path, .Platform$file.sep) if (!str_ends(path, .Platform$file.sep)) str_c(path, .Platform$file.sep)
+11 -1
View File
@@ -233,6 +233,11 @@ parse_speakerlist <- function(speakerliste_xml) {
#' #'
#' @export #' @export
write_to_csv <- function(tables, path="inst/csv/", create=F) { write_to_csv <- function(tables, path="inst/csv/", create=F) {
is_valid_res(tables)
stopifnot("path must be of type character" = is.character(path))
stopifnot("create must be of type logical" = is.logical(create))
path <- make_directory_path(path)
check_directory(path, create) check_directory(path, create)
write.table(tables$speaker, str_c(path, "speaker.csv")) write.table(tables$speaker, str_c(path, "speaker.csv"))
write.table(tables$speeches, str_c(path, "speeches.csv")) write.table(tables$speeches, str_c(path, "speeches.csv"))
@@ -250,6 +255,9 @@ write_to_csv <- function(tables, path="inst/csv/", create=F) {
#' #'
#' @export #' @export
read_from_csv <- function(path="inst/csv/") { read_from_csv <- function(path="inst/csv/") {
stopifnot("path must be of type character" = is.character(path))
path <- make_directory_path(path)
list(speaker = read.table(str_c(path, "speaker.csv")) %>% list(speaker = read.table(str_c(path, "speaker.csv")) %>%
tibble() %>% tibble() %>%
mutate(id = as.character(id)), mutate(id = as.character(id)),
@@ -259,5 +267,7 @@ read_from_csv <- function(path="inst/csv/") {
date = as.Date(date)), date = as.Date(date)),
talks = tibble %$% read.table(str_c(path, "talks.csv")), talks = tibble %$% read.table(str_c(path, "talks.csv")),
comments = tibble %$% read.table(str_c(path, "comments.csv")), comments = tibble %$% read.table(str_c(path, "comments.csv")),
applause = tibble %$% read.table(str_c(path, "applause.csv"))) applause = tibble %$% read.table(str_c(path, "applause.csv"))) -> res
is_valid_res(res)
res
} }
+156 -91
View File
@@ -1,19 +1,166 @@
# How to develop # Description
R package to analyze parliamentary records of the 19th legislative period of the Bundestag,
the German parliament.
# Installation
Using the `remotes` package, this is easily installed via:
```r
remotes::install_url("https://git.flavigny.de/christian/hateimparlament/archive/master.zip")
```
Since the fetching and reading is very slow and depends on an internet connection, all vignettes
use `read_from_csv` to read already parsed tibbles from `.csv` files.
That's why, if you want to build the vignettes yourself, you need to
download the source code, e.g. on Linux
```
git clone https://git.flavigny.de/christian/hateimparlament
cd hateimparlament
```
then start `R` and do
```r
devtools::load_all()
fetch_all(create = TRUE)
read_all() %>% repair() -> res
write_to_csv(res, create = TRUE)
```
Then finally, do:
```r
devtools::install(build_vignettes = TRUE)
```
# Features
The package mainly supplies 4 functionalities:
## Download records
To analyze records, they need to be downloaded. This is done with `fetch_all`:
```r
fetch_all("records/", create = TRUE) # path to directory where records should be stored
```
This downloads all parliamentary records and stores them as `.xml` files in the given directory.
## Parse records
To use the records in R, they are converted to `tibble`s with
```r
res_raw <- read_all("records/") # path to directory where records are stored
```
`res_raw` is a named list with 5 `tibble`s:
### Speaker
Table of all speakers of this legislative period.
Fields:
- `id`: Unique speaker id
- `prename`: Prename
- `lastname`: Surname
- `fraction`: Name of fraction if the speaker is member of parliament.
- `title`: Title, e.g. ,,Prof''
- `role_short`: Short name of role, e.g. ,,Bundeskanzlerin''
- `role_long`: Long name of role
### Speeches
Table of all speeches given during this legislative period.
Fields:
- `id`: Unique speech id
- `speaker`: Principal speaker (the person standing behind the lectern during the speech).
- `date`: Date of session
### Talks
Within a speech, there can be multiple talks by different people. Mostly this is the main speech
by the principal speaker, but usually there are questions by other members of parliament or
order calls by the president of the Bundestag.
Fields:
- `speech_id`: Speech in which this talk has been given
- `speaker`: Person that actually talks
- `content`: Spoken content
### Comments
These are the interjections that appear during the speeches.
Fields:
- `speech_id`: The speech that was interrupted
- `on_speaker`: The speaker who was interrupted
- `fraction`: The fraction of the commenter
- `commenter`: The person who interrupted the speech
- `comment`: The content of the comment
### Applause
Table containing all the rounds of applause that happened during this legislative period.
Fields:
- `speech_id`: Speech during which was applauded
- `on_speaker`: Speaker who was applauded
And then logical fields `CDU_CSU`, `SPD`, `FDP`, `DIE_LINKE`, `BUENDNIS_90_DIE_GRUENEN`, `AfD`
for every fraction in the Bundestag, signifying whether this fraction applauded.
## Repair records
The parliamentary records usually contain some major and minor formatting issues. These are
mostly resolved by using
```
res <- repair(res_raw)
```
By passing `lookup_speaker = TRUE`, even commenters in
`res_raw$comments` are matched with their respective speaker id.
## Analysis
Also some functions are provided to analyze the parliamentary records and draw some plots:
- `bar_plot_fractions`
- `find_word`
- `join_speaker`
- `word_usage_by_date`
See their usage with the `?` operator.
In the vignettes you can find different analyses of the protocols, for example:
- "Who talks the most?"
- "Which party gives the most speeches?"
- "Which party comments the most on which parties?"
- "When are which topics discussed the most?"
- ...
# Contributing
Developing works the easiest with `devtools`:
```r ```r
# everything works with devtools (loads some other packages too)
library(devtools) library(devtools)
```
# reload all package functions When you changed something or added some functionality, you can reload all package functions with
```r
load_all() load_all()
```
If you want to avoid reading all records every time you start a new R session, you can
write your parsed tibbles to CSV files:
#write to CSV files to speed up loading ```
tables <- read_all() tables <- read_all()
tables <- repair(tables) tables <- repair(tables)
write_to_csv(tables) write_to_csv(tables, "path/to/csv/")
``` ```
We NEVER use source(...), etc.! Also NEVER use library(...). Then later you can use
But to add new packages (as dependency), use: ```r
res <- read_from_csv("path/to/csv/")
```
to load your stored tibbles very fast.
NEVER use source(...), etc.! Also NEVER use library(...).
To add new packages (as dependency), use:
```r ```r
use_package("my-good-old-package") use_package("my-good-old-package")
``` ```
@@ -27,87 +174,5 @@ document()
Build vignettes Build vignettes
```r ```r
rmarkdown::render("vignettes/bla.Rmd") rmarkdown::render("vignettes/test.Rmd")
``` ```
# Download
Before parsing, fetch.R must be run to download all protocols.
```r
fetch_all("../inst/records/") # path to directory where records should be stored
```
# Parsing
## tables
parse.R parses all downloaded logs and creates 5 tibbles.
repair.R then cleans up the errors in these tibbles.
```r
read_all("../inst/records/") %>% repair()
```
### Speaker
structure: `id` , `first_name` , `last_name` , `fraction` , `title` , `role_short`, `role_long`.
Obtained from the `<speaker list>` entry at the end of the transcripts.
### Speeches
Structure: `id` , `speaker`
The speeches `id` is specified in the protocol and is unique.A speech is a `<speech>` entry in the session history. A speech always has a main speaker (the one standing at the front of the lectern).
Within a speech, there can be different speech entries:
- Comments: Applause, interjections, etc.
- Speeches: Typically mainly the main speaker, but also interjections.
These are stored in the talks, comments and applause tables when parsing.
### Talks
Structure: `speech_id` , `speaker` , `content`.
These are the actual talk entries that appear within _speeches_.
- `speech_id`: the speech in which the contribution appears.
- `speaker`: The speaker of the speech entry.
- `content`: The content of the speech.
###comments
These are the interjections that appear during the speeches.
They have the following structure:
- `speech_id`: The speech that was interrupted.
- `on_speaker`: The speaker who was interrupted.
- `fraction`
- `commenter`: The person who interrupted the speech.
- `comment`: The content of the comment.
###applause
The logical table shows which party applauded for which speaker with explicit speech and which did not.
structure: `speech_id`, `on_speaker`, `CDU_CSU`, `SPD`, `FDP`, `DIE_LINKE`, `BUENDNIS_90_DIE_GRUENEN`, `AfD`
# Analysis
analysis.R provides some functions to analyze the "Plenarprotokolle" and to create plots.
In the vignettes you can find different analyses of the protocols, for example:
- "Who talks the most?"
- "Which party gives the most speeches?"
- "Which party comments the most on which parties?"
- "When are which topics discussed the most?"
- ...
+108989
View File
File diff suppressed because it is too large Load Diff
+18 -3
View File
@@ -4,15 +4,30 @@
\name{hateimparlament-package} \name{hateimparlament-package}
\alias{hateimparlament} \alias{hateimparlament}
\alias{hateimparlament-package} \alias{hateimparlament-package}
\title{hateimparlament: Protocolanalysis of German Bundestag} \title{hateimparlament: Recordanalysis Of Bundestag}
\description{ \description{
Downloads, parses and analyses protocols of the current German parliament (Bundestag). Downloads, parses and analyses parliamentary records of the 19th legislative
period of the German parliament (Bundestag).
} }
\details{ \details{
hateimparlament ist ein großartiges Paket! hateimparlament ist ein großartiges Paket!
}
\seealso{
Useful links:
\itemize{
\item \url{https://git.flavigny.de/christian/hateimparlament}
\item Report bugs at \url{https://git.flavigny.de/christian/hateimparlament/issues}
}
} }
\author{ \author{
\strong{Maintainer}: First Last \email{first.last@example.com} (\href{https://orcid.org/YOUR-ORCID-ID}{ORCID}) \strong{Maintainer}: Christian Merten \email{christian@merten.dev}
Authors:
\itemize{
\item Leon Burgard
\item Josua Kugler
}
} }
\keyword{internal} \keyword{internal}
+2 -2
View File
@@ -1,8 +1,8 @@
--- ---
title: "explicittopic" title: "Analysis of covered topics"
output: rmarkdown::html_vignette output: rmarkdown::html_vignette
vignette: > vignette: >
%\VignetteIndexEntry{explicittopic} %\VignetteIndexEntry{Analysis of covered topics}
%\VignetteEngine{knitr::rmarkdown} %\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8} %\VignetteEncoding{UTF-8}
--- ---
+27 -49
View File
@@ -1,8 +1,8 @@
--- ---
title: "genderequality" title: "Differences in gender"
output: rmarkdown::html_vignette output: rmarkdown::html_vignette
vignette: > vignette: >
%\VignetteIndexEntry{genderequality} %\VignetteIndexEntry{Differences in gender}
%\VignetteEngine{knitr::rmarkdown} %\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8} %\VignetteEncoding{UTF-8}
--- ---
@@ -20,7 +20,7 @@ library(dplyr)
library(ggplot2) library(ggplot2)
library(stringr) library(stringr)
library(tidyr) library(tidyr)
library(rvest) library(xml2)
``` ```
## Preparation of data ## Preparation of data
@@ -33,8 +33,7 @@ Second, those `.xml` files, need to be parsed into `R` `tibbles`. This is accomp
```r ```r
read_all("../records/") %>% repair() -> res read_all("../records/") %>% repair() -> res
``` ```
We also used `repair` to fix a bunch of formatting issues in the records and unpacked We also used `repair` to fix a bunch of formatting issues in the records.
the result into more descriptive variables.
For development purposes, we load the tables from csv files. For development purposes, we load the tables from csv files.
```{r} ```{r}
@@ -48,53 +47,33 @@ speaker <- res$speaker
talks <- res$talks talks <- res$talks
``` ```
Bevor we can do our analysis, we have to assign a gender to our politicans. Bevor we can do our analysis, we have to assign a gender to our politicans. We do this
by reading the gender from the master data of all members of parliament, which is
fetched from bundestag.de.
```{r} ```{r}
extract_href <- function(sel, html) { xml_get <- function(node, name) {
html %>% res <- xml_text(xml_find_all(node, name))
html_node(sel) %>% if (length(res) == 0) NA_character_
html_attr("href") else res
} }
first_content_p_text <- function(url) { x <- read_xml("../inst/masterdata.xml")
res <- NA mdbs <- xml_find_all(x, "MDB")
i <- 1
while(is.na(res)) { ids <- c()
read_html(url) %>% genders <- c()
html_node(str_glue("#mw-content-text > div.mw-parser-output > p:nth-child({i})")) %>% for (mdb in mdbs) {
html_text() -> res xml_get(mdb, "ID") -> mdb_id
i <- i + 1 xml_find_first(mdb, "BIOGRAFISCHE_ANGABEN") %>%
} xml_get("GESCHLECHT") ->
res mdb_gender
ids <- c(ids, mdb_id)
genders <- c(genders, if (mdb_gender == "männlich") "male" else "female")
} }
abgeordneten_list_html <- read_html( gender <- tibble(id = ids, gender = genders)
"https://de.wikipedia.org/wiki/Liste_der_Mitglieder_des_Deutschen_Bundestages_(19._Wahlperiode)") speaker_with_gender <- left_join(res$speaker, gender)
selectors <- str_glue("#mw-content-text > div.mw-parser-output > table:nth-child(20) > tbody > tr:nth-child({2:709}) > td:nth-child(2) > a")
link_part2 <- sapply(selectors, extract_href, abgeordneten_list_html)
link <- str_c("https://de.wikipedia.org", link_part2)
text <- sapply(link, first_content_p_text)
text %>%
str_extract(" ist ein.") %>%
str_replace(" ist eine", "female") %>%
str_replace(" ist ein ", "male") ->
gender
text %>%
str_extract("^([:upper:]?[:lower:]+[\\s\\-]?)*") %>%
str_trim() ->
names
gender <- tibble(speaker = names,
gender = gender)
speaker %>%
unite("speaker", vorname, nachname, sep = " ") %>%
right_join(gender, by = "speaker") ->
speaker_with_gender
``` ```
## Analyse ## Analyse
@@ -179,9 +158,8 @@ speeches %>%
summarize(n = n()) %>% summarize(n = n()) %>%
ungroup() %>% ungroup() %>%
arrange(-n) %>% arrange(-n) %>%
left_join(speaker, by=c("speaker" = "id")) %>% join_speaker(res) %>%
unite(name, vorname, nachname, sep = " ") %>% left_join(gender, by=c("speaker"="id")) %>%
inner_join(gender, by=c("name"= "speaker")) %>%
group_by(gender) %>% group_by(gender) %>%
summarise(absolute=sum(n)) %>% summarise(absolute=sum(n)) %>%
filter(gender %in% c("female", "male")) %>% filter(gender %in% c("female", "male")) %>%
+2 -2
View File
@@ -1,8 +1,8 @@
--- ---
title: "generalquestions" title: "General questions"
output: rmarkdown::html_vignette output: rmarkdown::html_vignette
vignette: > vignette: >
%\VignetteIndexEntry{generalquestions} %\VignetteIndexEntry{General questions}
%\VignetteEngine{knitr::rmarkdown} %\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8} %\VignetteEncoding{UTF-8}
--- ---
+2 -2
View File
@@ -1,8 +1,8 @@
--- ---
title: "interaction" title: "Interaction between fractions"
output: rmarkdown::html_vignette output: rmarkdown::html_vignette
vignette: > vignette: >
%\VignetteIndexEntry{interaction} %\VignetteIndexEntry{Interaction between fractions}
%\VignetteEngine{knitr::rmarkdown} %\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8} %\VignetteEncoding{UTF-8}
--- ---