Ikke-kategoriseret

Aarhus Byraads forhandlingsprotokoller 1930-1940 term frequency(tf) – inverse document frequency(idf)

Denne rapport dokumenterer databehandlingen af data fra Aarhus Stadsarkivs Github-repository1. I dette tilfælde er der taget udgangspunkt i den del af datasættet der omhandler årene fra 1930 til 1940.

Datasættet er struktureret som følger:

DESCRIPTION
The datasets consist of the transcribed and proof-read text from the annually printed minutes. Text from one specific agenda item on one specific page produces one row. If the same agenda item runs across several pages, it just produces several rows of text.

Each row has the following columns:

date_of_meeting
The date of the meeting (yyyy-mm-dd)

publication_page
The original pagenumber from the printed minutes

page_url
Link to a scanned copy of the printed page

record_ids
One or more record_ids that the current agenda item references. The ids are assigned by the City Council

text
The transcribed text”[2]

Datasættet er behandlet i statistik-programmet R, der giver mange muligheder for statistisk arbejde og efterfølgende grafisk fremstilling af resultaterne. I R arbejder man med pakker, som tilføjer forskellige funktionaliteter til grundstammen af R-funktioner. I dette tilfælde er de relevante pakker:

library(tidyverse)
library(tidytext)
library(lubridate)
library(ggplot2)

Dokumentation for de enkelte pakker:
*https://www.tidyverse.org/packages/
*https://cran.r-project.org/web/packages/tidytext/vignettes/tidytext.html
*https://lubridate.tidyverse.org/
*https://ggplot2.tidyverse.org/

For mere information om R generelt:
https://www.r-project.org/

Indlæsning af data

Først indlæses datasættet i R. Dette gøre med et link til datasættet på Aarhus Stadsarkivs github:

meetings_1930_1940 <- read_csv("https://raw.githubusercontent.com/aarhusstadsarkiv/datasets/master/minutes/city-council/city-council-minutes-1930-1940.csv")

 

Oprensning af data

Data behandlingen vil tage udgangspunkt i Tidy Data-princippet som den er implementeret i tidytext-pakken. Tankegangen er her at tage en tekst og splitte den op i enkelte ord. På denne måde optræder der kun ét ord per række i datasættet. Dette er dog et problem i forhold til propier på formen “M. O. Pedersen”. Ved brug af tidytext princippet vil dette proprium blive til “M”, “O”, og “Pedersen” på hver sin række. Alt tegnsætning fjernes af tidytext-formattet, hvorfor det kun er “M” og “O”, der fremgår. Herved opstår der altså et meningstab i og med at “M” og “O” for sig selv ikke gør os klogere. Dette meningstab er vi interesseret i at undgå og dette gøres ved hjælp af regulære udtryk som:

“([A-Z]). ([A-Z]). ([A-z-]+)”, “\1\2\3”

Dette dette udtryk får R til at lede efter alle tilfælde hvor et stort bogstav efterfølges af et punktum, et mellemrum, et stort bogstav, et punktum, et mellemrum og et stort bogstav efterfulgt af et eller flere små bogstaver. Herefter erstattes punktummerne og mellemrummene med tegnet “_”, således at:

“M. O. Pedersen” ændres til “M_O_Pedersen”

Ved et kig på mødereferaterne kan man se, at propriet “Christian” forkortes “Chr.” efterfulgt af et efternavn. Det og lignende tilfælde er også søgt løst med regulære udtryk som vist herunder:

meetings_1930_1940 %>% 
  mutate(text = 
           str_replace_all(
                          text, 
                          pattern = 
                            "([A-Z])\\. ([A-Z])\\. ([A-z-]+)", "\\1_\\2_\\3")) %>%
  mutate(text = 
           str_replace_all(
                          text, 
                          pattern = 
                            "([A-Z])\\. ([A-Z])\\. ([A-Z])\\. ([A-z-]+)", 
                            "\\1_\\2_\\3_\\4")) %>% 
  mutate(text = 
           str_replace_all(
                          text, 
                          pattern = 
                            "([A-Z])\\. ([A-Z][a-z]+)", "\\1_\\2")) %>% 
  mutate(text = 
           str_replace_all(
                          text, 
                          pattern = 
                            "Chr\\. ([A-z-]+)", "Chr_\\1" )) %>% 
  mutate(text = 
           str_replace_all(
                          text, 
                          pattern = 
                            "Vald\\. ([A-z]+)", "Vald_\\1")) -> meetings_1930_1940

Dette kan muligvis vise sig at være utilstrækkeligt, da andre navne kan forkortes på lignende måder lige så vel som der kan være flere mellemnavne end de 3, der bliver kodet efter her.

I denne undersøgelse er ønsket at finde de vigtigste ord pr. år i Aarhus Byråds forhandlingsprotokoller. Problemet er imidlertidig, at tidsformatet i Stadsarkivets data er en dato på formen ÅÅÅÅ-MM-DD. Da vi her kun er interesseret i året, kan vi takket være pakken lubridate med funktionen ‘year’ udtrække året og sætte den over i sin egen kolonne:

meetings_1930_1940 %>% 
  mutate(aar = year(date_of_meeting)) %>% 
  select(aar, text, record_ids)
## # A tibble: 14,508 x 3
##      aar text                                                   record_ids
##    <dbl> <chr>                                                  <chr>     
##  1  1930 Mødet den 3. April 1930. (For lukkede Døre). Fraværen… <NA>      
##  2  1930 Indstilling fra Skolekommission og Skoleudvalg angaae… 54-1930   
##  3  1930 IV. 1. J_Jensen, do. 2. E_Nordvig-Petersen, do. 3. K_… 54-1930   
##  4  1930 Ting talte for, at man ikke skulde forbigaa de gifte … 54-1930   
##  5  1930 "Andragende fra Restauratør Carl Hansen, \"Pavillonen… 47-1930   
##  6  1930 Andragende fra Drejer H_C_Salbro om Eftergivelse af F… 17_31-1929
##  7  1930 Andragende fra Arbejdsmand Aage Nielsen om Eftergivel… 17_39-1929
##  8  1930 Andragende fra Enke Amalie Rodenberg om Eftergivelse … 17_38-1929
##  9  1930 Mødet den 3. April 1930. Fraværende: Chr_Nielsen og V… <NA>      
## 10  1930 Indenrigsministeriets Samtykke til Køb af Ejendommen … 716-1929  
## # ... with 14,498 more rows

Det næste der sker er, at vi omdanner data til det førnævnte tidytextformat, hvor hvert ord kommer til at stå på en række for sig selv:, hvilket gøres med unnest_tokens-funktionen:

meetings_1930_1940 %>% 
  mutate(aar = year(date_of_meeting)) %>% 
  select(aar, text, record_ids) %>% 
  unnest_tokens(word, text)
## # A tibble: 1,157,594 x 3
##      aar record_ids word       
##    <dbl> <chr>      <chr>      
##  1  1930 <NA>       mødet      
##  2  1930 <NA>       den        
##  3  1930 <NA>       3          
##  4  1930 <NA>       april      
##  5  1930 <NA>       1930       
##  6  1930 <NA>       for        
##  7  1930 <NA>       lukkede    
##  8  1930 <NA>       døre       
##  9  1930 <NA>       fraværende 
## 10  1930 <NA>       chr_nielsen
## # ... with 1,157,584 more rows

Analyse

Vi er nu interesserede i at finde de ord, der hyppigst forekommer pr. år i årene 1930-1940, som vores datasæt spænder over.

meetings_1930_1940 %>% 
  mutate(aar = year(date_of_meeting)) %>% 
  select(aar, text, record_ids) %>% 
  unnest_tokens(word, text) %>% 
  count(aar, word, sort = TRUE)
## # A tibble: 125,715 x 3
##      aar word      n
##    <dbl> <chr> <int>
##  1  1935 at     4979
##  2  1938 at     4010
##  3  1936 at     3949
##  4  1931 at     3922
##  5  1934 at     3863
##  6  1939 at     3818
##  7  1937 at     3732
##  8  1935 til    3499
##  9  1932 at     3374
## 10  1933 at     3238
## # ... with 125,705 more rows

Ikke overraskende er det småord, som optræder flest gange pr. år. Dette er ikke videre interessant i denne undersøgelse, så vi er nu interesseret i at finde et mål, der gør at vi kan sammenligne ords hyppighed på tværs af årene. Dette kan vi gøre ved at udregne ordets, termets, frekvens:

\[f = \frac{n_{term}}{N_{aar}}\]

Før vi kan tage dette skridt skal vi dog have R til at tælle, hvor mange ord, der er i de enkelte år. Dette gøres med funktionen group_by efterfulgt af summarise:

meetings_1930_1940 %>% 
  mutate(aar = year(date_of_meeting)) %>% 
  select(aar, text, record_ids) %>% 
  unnest_tokens(word, text) %>% 
  count(aar, word, sort = TRUE) %>% 

  group_by(aar) %>% 
  summarise(total = sum(n)) -> total_words


total_words
## # A tibble: 11 x 2
##      aar  total
##    <dbl>  <int>
##  1  1930  71014
##  2  1931 115062
##  3  1932 102449
##  4  1933 102346
##  5  1934 117568
##  6  1935 140307
##  7  1936 117619
##  8  1937 115134
##  9  1938 120169
## 10  1939 120286
## 11  1940  35640

Herefter skal vi have tilføjet det totale antal ord til vores dataframe, hvilket gøres med left_join:

meetings_1930_1940 %>% 
  mutate(aar = year(date_of_meeting)) %>% 
  select(aar, text, record_ids) %>% 
  unnest_tokens(word, text) %>% 
  count(aar, word, sort = TRUE) %>% 

  left_join(total_words, by = "aar") -> meetings_1930_1940
meetings_1930_1940
## # A tibble: 125,715 x 4
##      aar word      n  total
##    <dbl> <chr> <int>  <int>
##  1  1935 at     4979 140307
##  2  1938 at     4010 120169
##  3  1936 at     3949 117619
##  4  1931 at     3922 115062
##  5  1934 at     3863 117568
##  6  1939 at     3818 120286
##  7  1937 at     3732 115134
##  8  1935 til    3499 140307
##  9  1932 at     3374 102449
## 10  1933 at     3238 102346
## # ... with 125,705 more rows

Nu har vi de tal vi skal bruge for at udregne ordenes frekvenser. Her udregner vi for “at” i 1935.

\[f(\textrm{at})=\frac{4979}{140307}=0,0354864690\]

Ved at udregne frekvensen for termer kan vi sammenligne dem på tværs af år. Det er dog ikke videre interessant at sammenligne brugen af ordet “at” årene i mellem. Vi mangler derfor en måde at “straffe” ord som optræder hyppigt i alle årene. Til dette kan vi bruge inversed document frequency(idf):
\[\textrm{idf}(term)=\ln(\frac{n}{N})\]
Hvor n er det totale antal dokumenter(i vores tilfælde år) og N er antallet af år, hvor ordet fremgår.

\[\textrm{idf}(at)=\ln(\frac{10}{10})=0\]
Herved får vi altså straffet ord som optræder med stor hyppighed i alle årene eller mange af årene. Ord der forekommer i alle årene kan altså altså ikke fortælle os noget særlig om et givent år. Disse ord vil have en idf på 0 hvorfor deres tf_idf også bliver 0, da denne er defineret ved tf gange med idf.

Heldigvis kan R udregne tf og tf_idf for alle ordene for os i et snuptag med bind_tf_idf-funktionen:

meetings_1930_1940 <- meetings_1930_1940 %>% 
  bind_tf_idf(word, aar, n)
meetings_1930_1940
## # A tibble: 125,715 x 7
##      aar word      n  total     tf   idf tf_idf
##    <dbl> <chr> <int>  <int>  <dbl> <dbl>  <dbl>
##  1  1935 at     4979 140307 0.0355     0      0
##  2  1938 at     4010 120169 0.0334     0      0
##  3  1936 at     3949 117619 0.0336     0      0
##  4  1931 at     3922 115062 0.0341     0      0
##  5  1934 at     3863 117568 0.0329     0      0
##  6  1939 at     3818 120286 0.0317     0      0
##  7  1937 at     3732 115134 0.0324     0      0
##  8  1935 til    3499 140307 0.0249     0      0
##  9  1932 at     3374 102449 0.0329     0      0
## 10  1933 at     3238 102346 0.0316     0      0
## # ... with 125,705 more rows

Ikke desto mindre ser vi ikke nogen interessant ord. Dette skyldes at R lister ordene op i et stigende hierarki – altså lavest til højst.
Vi beder det om at gøre det faldende i stedet – højest tf_idf

meetings_1930_1940 %>% 
  select(-total) %>% 
  arrange(desc(tf_idf))
## # A tibble: 125,715 x 6
##      aar word              n       tf   idf   tf_idf
##    <dbl> <chr>         <int>    <dbl> <dbl>    <dbl>
##  1  1938 1938            452 0.00376  0.452 0.00170 
##  2  1940 flyvepladsen     18 0.000505 1.70  0.000861
##  3  1933 j_chr_møller    103 0.00101  0.788 0.000793
##  4  1940 eksercerplads    16 0.000449 1.70  0.000765
##  5  1932 j_chr_møller     92 0.000898 0.788 0.000708
##  6  1937 raadhus          99 0.000860 0.788 0.000678
##  7  1940 stadsdyrlægen    10 0.000281 2.40  0.000673
##  8  1936 1936            245 0.00208  0.318 0.000663
##  9  1939 1939            392 0.00326  0.201 0.000654
## 10  1930 j_chr_møller     56 0.000789 0.788 0.000622
## # ... with 125,705 more rows

Vi ser her at 1938, 1936 og 1939 kommer ret højt på listen. Dette skyldes formentlig at journalnumrene dannes udfra det pågældende år. Inden vi laver en grafisk visualisering, fjerner vi derfor alle årstal i teksten.

stopord <- data_frame(word = c("1930", "1931", "1932", "1933", "1934", "1935", 
                                   "1936", "1937", "1938", "1939", "1940"))
meetings_1930_1940 <- anti_join(meetings_1930_1940, stopord, by = "word")

Visualisering

Herefter kan vi gå over til en grafisk visualisering.

meetings_1930_1940 %>%
  arrange(desc(tf_idf)) %>%
  mutate(word = factor(word, levels = rev(unique(word)))) %>% 
  group_by(aar) %>% 
  top_n(15) %>% 
  ungroup %>%
  ggplot(aes(word, tf_idf)) +
  geom_col(show.legend = FALSE, fill = "skyblue2") +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~aar, ncol = 3, scales = "free") +
  scale_y_continuous(labels = scales::comma_format(accuracy = 0.0001)) +
  coord_flip()
## Selecting by tf_idf

Klik for større billede

[1]: https://github.com/aarhusstadsarkiv/datasets/tree/master/minutes/city-council
[2]: Indsat fra https://github.com/aarhusstadsarkiv/datasets/tree/master/minutes/city-council

Posted by Max Odsbjerg Pedersen in Ikke-kategoriseret, 0 comments