How often do you read The Student Life?
a. Every day
b. 3-5 times a week
c. Once a week
d. Rarely
Reading The Student Life
What do you think is the most common word in the titles of The Student Life opinion pieces?
Analyzing The Student Life
Reading The Student Life
How do you think the sentiments in opinion pieces in The Student Life compare across authors?
Roughly the same?
Wildly different?
Somewhere in between?
Analyzing The Student Life
All of this analysis is done in R!
(mostly) with tools you already know!
Common words in The Student Life titles
Code for the earlier plot:
data(stop_words) # from tidytexttsl_opinion_titles |> tidytext::unnest_tokens(word, title) |>anti_join(stop_words) |>count(word, sort =TRUE) |>slice_head(n =20) |>mutate(word =fct_reorder(word, n)) |>ggplot(aes(y = word, x = n, fill =log(n))) +geom_col(show.legend =FALSE) +theme_minimal(base_size =16) +labs(x ="Number of mentions",y ="Word",title ="The Student Life - Opinion pieces",subtitle ="Common words in the 500 most recent opinion pieces",caption ="Source: Data scraped from The Student Life on Nov 4, 2024" ) +theme(plot.title.position ="plot",plot.caption =element_text(color ="gray30") )
Avg sentiment scores of first paragraph
Code for the earlier plot:
afinn_sentiments <-get_sentiments("afinn") # need tidytext and textdatatsl_opinion_titles |> tidytext::unnest_tokens(word, first_p) |>anti_join(stop_words) |>left_join(afinn_sentiments) |>group_by(authors, title) |>summarize(total_sentiment =sum(value, na.rm =TRUE), .groups ="drop") |>group_by(authors) |>summarize(n_articles =n(),avg_sentiment =mean(total_sentiment, na.rm =TRUE), ) |>filter(n_articles >1&!is.na(authors)) |>arrange(desc(avg_sentiment)) |>slice(c(1:10, 69:78)) |>mutate(authors =fct_reorder(authors, avg_sentiment),neg_pos =if_else(avg_sentiment <0, "neg", "pos"),label_position =if_else(neg_pos =="neg", 0.25, -0.25) ) |>ggplot(aes(y = authors, x = avg_sentiment)) +geom_col(aes(fill = neg_pos), show.legend =FALSE) +geom_text(aes(x = label_position, label = authors, color = neg_pos),hjust =c(rep(1,10), rep(0, 10)),show.legend =FALSE,fontface ="bold" ) +geom_text(aes(label =round(avg_sentiment, 1)),hjust =c(rep(1.25,10), rep(-0.25, 10)),color ="white",fontface ="bold" ) +scale_fill_manual(values =c("neg"="#4d4009", "pos"="#FF4B91")) +scale_color_manual(values =c("neg"="#4d4009", "pos"="#FF4B91")) +scale_x_continuous(breaks =-5:5, minor_breaks =NULL) +scale_y_discrete(breaks =NULL) +coord_cartesian(xlim =c(-5, 5)) +labs(x ="negative ← Average sentiment score (AFINN) → positive",y =NULL,title ="The Student Life - Opinion pieces\nAverage sentiment scores of first paragraph by author",subtitle ="Top 10 average positive and negative scores",caption ="Source: Data scraped from The Student Life on Nov 4, 2024" ) +theme_void(base_size =16) +theme(plot.title =element_text(hjust =0.5),plot.subtitle =element_text(hjust =0.5, margin =unit(c(0.5, 0, 1, 0), "lines")),axis.text.y =element_blank(),plot.caption =element_text(color ="gray30") )
# A tibble: 500 × 4
title authors date first_p
<chr> <chr> <dttm> <chr>
1 Elon Musk’s million-dollar-a-day rewards… Celest… 2024-11-01 16:27:00 have y…
2 The politics behind apolitical acts Eric Lu 2024-11-01 16:21:00 while …
3 In Defense of the Pomona College Judicia… Henri … 2024-11-01 16:15:00 former…
4 ‘Yakking’ isn’t a canon event, party res… Kabir … 2024-11-01 16:10:00 whirri…
5 The ‘if he wanted to, he would’ mentalit… Tess M… 2024-11-01 16:01:00 ladies…
6 You can’t silence us: A united front aga… Outbac… 2024-10-25 11:23:00 in the…
7 We will not tolerate collective punishme… Suspen… 2024-10-25 09:04:00 in the…
8 A guide to ballot propositions Akshay… 2024-10-25 06:37:00 are yo…
9 Pomona will protest or perish Maggie… 2024-10-23 06:00:00 as pom…
10 GUEST EDITORIAL: Pomona’s culpability un… Mike W… 2024-10-11 08:23:00 mike w…
# ℹ 490 more rows
Web scraping
Scraping the web: what? why?
Increasing amount of data is available on the web
These data are provided in an unstructured format: you can always copy & paste, but it’s time-consuming and prone to errors
Web scraping is the process of extracting this information automatically and transform it into a structured dataset
Two different scenarios:
Screen scraping: extract data from source code of website, with html parser (easy) or regular expression matching (less easy).
Web APIs (application programming interface): website offers a set of structured http requests that return JSON or XML files.
Hypertext Markup Language
Most of the data on the web is largely available as HTML - while it is structured (hierarchical) it often is not available in a form useful for analysis (flat / tidy).
<html><head><title>This is a title</title></head><body><p align="center">Hello world!</p><br/><div class="name" id="first">John</div><div class="name" id="last">Doe</div><div class="contact"><div class="home">555-555-1234</div><div class="home">555-555-2345</div><div class="work">555-555-9999</div><div class="fax">555-555-8888</div></div></body></html>
Some HTML elements
<html>: start of the HTML page
<head>: header information (metadata about the page)
<body>: everything that is on the page
<p>: paragraphs
<b>: bold
<table>: table
<div>: a container to group content together
rvest
The rvest package makes basic processing and manipulation of HTML data straight forward
It is designed to work with pipelines built with |>
We will use a tool called SelectorGadget to help us identify the HTML elements of interest by constructing a CSS selector which can be used to subset the HTML document.
Some examples of basic selector syntax is below,
Selector
Example
Description
.class
.title
Select all elements with class=“title”
#id
#name
Select all elements with id=“name”
element
p
Select all <p> elements
element element
div p
Select all <p> elements inside a <div> element
element>element
div > p
Select all <p> elements with <div> as a direct parent
[attribute]
[class]
Select all elements with a class attribute
[attribute=value]
[class=title]
Select all elements with class=“title”
Agenda 11/6/24
rvest continues
example
ethics
CSS classes and ids
class and id are used to style elements (e.g., change their color!). They are special types of attributes.
class can be applied to multiple different elements (class is identified with ., for example .name)
id is unique to each element (id is identified with #, for example, #first)
html =read_html("<p> This is the first sentence in the paragraph. This is the second sentence that should be on the same line as the first sentence.<br>This third sentence should start on a new line. </p>")
html |>html_text()
[1] " \n This is the first sentence in the paragraph.\n This is the second sentence that should be on the same line as the first sentence.This third sentence should start on a new line.\n "
html |>html_text2()
[1] "This is the first sentence in the paragraph. This is the second sentence that should be on the same line as the first sentence.\nThis third sentence should start on a new line."
html_attr() always returns a string, so if you’re extracting numbers or dates, you’ll need to do some post-processing.
div p vs div > p
div p selects all <p> elements within <div>, regardless of depth.
div > p selects only direct child <p> elements of <div>.
<div>
<p>This will be selected by both `div p` and `div > p`.</p>
<section>
<p>This will be selected only by `div p`, not by `div > p`.</p>
</section>
</div>
SelectorGadget
SelectorGadget (selectorgadget.com) is a javascript based tool that helps you interactively build an appropriate CSS selector for the content you are interested in.
Recap
Use the SelectorGadget identify elements you want to grab
Use the rvest R package to first read in the entire page (into R) and then parse the object you’ve read in to the elements you’re interested in
Put the components together in a data frame (a tibble) and analyze it like you analyze any other data
Plan
Read in the entire page
Scrape opinion title and save as title
Scrape author and save as author
Scrape date and save as date
Create a new data frame called tsl_opinion with variables title, author, and date
title <- tsl_page |>html_elements(".entry-title a") |>html_text()title
[1] "OPINION: In Trump’s America, the future is dire for women"
[2] "OPINIONS: Catfished by a Scripps-sponsored internship"
[3] "OPINION: How maternal healthcare fails Black women"
[4] "OPINION: How ranked choice voting better serves us all"
[5] "OPINION: Elon Musk’s million-dollar-a-day rewards are undemocratic"
[6] "OPINION: The politics behind apolitical acts"
[7] "OPINION: In Defense of the Pomona College Judicial Council"
[8] "OPINION: ‘Yakking’ isn’t a canon event, party responsibly"
[9] "OPINION: The ‘if he wanted to, he would’ mentality is holding women back"
[10] "You can’t silence us: A united front against Pomona’s repression"
title <- title |>str_remove("OPINION: ")title
[1] "In Trump’s America, the future is dire for women"
[2] "OPINIONS: Catfished by a Scripps-sponsored internship"
[3] "How maternal healthcare fails Black women"
[4] "How ranked choice voting better serves us all"
[5] "Elon Musk’s million-dollar-a-day rewards are undemocratic"
[6] "The politics behind apolitical acts"
[7] "In Defense of the Pomona College Judicial Council"
[8] "‘Yakking’ isn’t a canon event, party responsibly"
[9] "The ‘if he wanted to, he would’ mentality is holding women back"
[10] "You can’t silence us: A united front against Pomona’s repression"
# A tibble: 10 × 3
title author date
<chr> <chr> <dttm>
1 In Trump’s America, the future is dire for women Tania… 2024-11-08 00:45:00
2 OPINIONS: Catfished by a Scripps-sponsored intern… Jada … 2024-11-07 22:39:00
3 How maternal healthcare fails Black women Chloe… 2024-11-07 22:38:00
4 How ranked choice voting better serves us all Alex … 2024-11-07 22:33:00
5 Elon Musk’s million-dollar-a-day rewards are unde… Celes… 2024-11-01 09:27:00
6 The politics behind apolitical acts Eric … 2024-11-01 09:21:00
7 In Defense of the Pomona College Judicial Council Henri… 2024-11-01 09:15:00
8 ‘Yakking’ isn’t a canon event, party responsibly Kabir… 2024-11-01 09:10:00
9 The ‘if he wanted to, he would’ mentality is hold… Tess … 2024-11-01 09:01:00
10 You can’t silence us: A united front against Pomo… Outba… 2024-10-25 04:23:00
Consider the following image. What do you think is wrong? (Hint: examine the y-axis carefully)
Reproduction of a data graphic reporting the number of gun deaths in Florida over time. The original image was published by Reuters. (Baumer, Kaplan, and Horton 2021)
More ethics: graphics
More ethics: graphics
A few weeks later, the Georgia Department of Health came out with the following two plots where, despite cases skyrocketing, they display images where the visual doesn’t really change.
More ethics: algorithms
disparate treatment \(\rightarrow\) means that the differential treatment is intentional
disparate impact \(\rightarrow\) means that the differential treatment is unintentional or implicit (some examples include advancing mortgage credit, employment selection, predictive policing)
More ethics: COMPAS
Dylan Fugett had three subsequent arrests for drug possession. Bernard Parker had no subsequent offenses.
More ethics: COMPAS
DYLAN FUGETT
BERNARD PARKER
Prior Offense
Prior Offense
1 attempted burglary
1 resisting arrest without violence
LOW RISK - 3
HIGH RISK - 10
Subsequent Offenses
Subsequent Offenses
3 drug possessions
None
More ethics: COMPAS
False positive and false negative rates broken down by race.