Setup and Data
Loading
Required
Packages
# Install any missing packages
required_pkgs <- c("readxl", "dplyr", "tidyr", "ggplot2", "knitr",
"kableExtra", "scales", "forcats", "psych", "janitor")
new_pkgs <- required_pkgs[!(required_pkgs %in% installed.packages()[, "Package"])]
if (length(new_pkgs)) install.packages(new_pkgs)
library(readxl)
library(dplyr)
library(tidyr)
library(ggplot2)
library(knitr)
library(kableExtra)
library(scales)
library(forcats)
library(psych)
library(janitor)
Load Data
# UPDATE THIS PATH to wherever you saved the file
df <- read_excel("bpsrs_full_dataset_2026.xlsx", sheet = "full dataset (use)")
cat("Rows:", nrow(df), "\n")
## Rows: 1471
cat("Columns:", ncol(df), "\n")
## Columns: 268
cat("Dataset labels:\n")
## Dataset labels:
print(table(df$label))
##
## dataset_one dataset_three dataset_two
## 213 342 916
Define Codebook
Mappings
All code-to-label mappings below are taken directly from the survey
instrument.
# ------------------------------------------------------------------
# Q2: Racial Identity
# ------------------------------------------------------------------
q2_labels <- c(
"1" = "American Indian or Alaskan Native",
"2" = "Asian American",
"3" = "Black/African American",
"4" = "Native Hawaiian or other Pacific Islander",
"5" = "White or Caucasian",
"6" = "Biracial or Multiracial (Black and another race)",
"7" = "Prefer to self-describe",
"8" = "African",
"9" = "Afro-Latina/o/x",
"10" = "Afro-Caribbean"
)
# ------------------------------------------------------------------
# Q3: Ethnicity
# ------------------------------------------------------------------
q3_labels <- c(
"1" = "Hispanic or Latina/o/x",
"2" = "Non-Hispanic or Latina/o/x",
"3" = "Prefer not to identify"
)
# ------------------------------------------------------------------
# Q4: Gender
# ------------------------------------------------------------------
q4_labels <- c(
"1" = "Male",
"2" = "Female",
"3" = "Non-binary / third gender",
"4" = "Transgender",
"5" = "Prefer to self-describe",
"6" = "Prefer not to respond"
)
# ------------------------------------------------------------------
# Q5: Sexual Orientation
# ------------------------------------------------------------------
q5_labels <- c(
"1" = "Asexual",
"2" = "Bisexual",
"3" = "Pansexual",
"4" = "Gay",
"5" = "Lesbian",
"6" = "Heterosexual or straight",
"7" = "Queer",
"8" = "Prefer to self-describe",
"9" = "Prefer not to respond"
)
# ------------------------------------------------------------------
# Q6: Age Group
# ------------------------------------------------------------------
q6_labels <- c(
"1" = "18-21 years old",
"2" = "21-34 years old",
"3" = "35-44 years old",
"4" = "45-54 years old",
"5" = "55-64 years old",
"6" = "65-74 years old",
"7" = "75 years or older"
)
# ------------------------------------------------------------------
# Q7: Highest Education
# ------------------------------------------------------------------
q7_labels <- c(
"1" = "Less than high school diploma",
"2" = "High school diploma or GED",
"3" = "Some college, but no degree",
"4" = "Trade/technical/vocational training",
"5" = "Associate degree",
"6" = "Bachelor's degree",
"7" = "Master's degree",
"8" = "Professional degree (MD, DDS, JD)",
"9" = "Doctorate degree (PhD, EdD)"
)
# ------------------------------------------------------------------
# Q8: Marital Status
# ------------------------------------------------------------------
q8_labels <- c(
"1" = "Single, never married",
"2" = "Married or domestic partnership",
"3" = "Widowed",
"4" = "Divorced",
"5" = "Separated"
)
# ------------------------------------------------------------------
# Q9: Employment Status
# ------------------------------------------------------------------
q9_labels <- c(
"1" = "Employed full-time",
"2" = "Employed part-time",
"3" = "Self-employed",
"4" = "Out of work, looking",
"5" = "Out of work, not looking",
"6" = "Homemaker",
"7" = "Student",
"8" = "Military",
"9" = "Retired",
"10" = "Unable to work"
)
# ------------------------------------------------------------------
# Q10: Work Location
# ------------------------------------------------------------------
q10_labels <- c(
"1" = "100% virtual",
"2" = "Mostly virtual",
"3" = "Half virtual / half physical",
"4" = "Mostly physical location",
"5" = "100% physical location"
)
# ------------------------------------------------------------------
# Q12: Household Income
# ------------------------------------------------------------------
q12_labels <- c(
"1" = "Less than $10,000",
"2" = "$10,000 to $14,999",
"3" = "$15,000 to $24,999",
"4" = "$25,000 to $34,999",
"5" = "$35,000 to $49,999",
"6" = "$50,000 to $74,999",
"7" = "$75,000 to $99,999",
"8" = "$100,000 to $149,999",
"9" = "$150,000 to $199,999",
"10" = "$200,000 or more"
)
# ------------------------------------------------------------------
# Q13: Professional Development Rating
# ------------------------------------------------------------------
q13_labels <- c(
"1" = "Very Poor",
"2" = "Poor",
"3" = "Unsure or not applicable",
"4" = "Good",
"5" = "Excellent"
)
# ------------------------------------------------------------------
# Q14: Hear About Events (Frequency)
# ------------------------------------------------------------------
q14_labels <- c(
"1" = "Never",
"2" = "Rarely",
"3" = "Occasionally",
"4" = "Frequently",
"5" = "Very Frequently"
)
# ------------------------------------------------------------------
# Q16: Event Participation Frequency
# ------------------------------------------------------------------
q16_labels <- c(
"1" = "Never",
"2" = "Rarely",
"3" = "Occasionally",
"4" = "Frequently",
"5" = "Very Frequently"
)
# ------------------------------------------------------------------
# Q17: Satisfaction with Events
# ------------------------------------------------------------------
q17_labels <- c(
"1" = "Highly dissatisfied",
"2" = "Dissatisfied",
"3" = "Somewhat dissatisfied",
"4" = "Neither satisfied nor dissatisfied",
"5" = "Somewhat satisfied",
"6" = "Satisfied",
"7" = "Highly satisfied"
)
# ------------------------------------------------------------------
# Q19: Entrepreneurship Support
# ------------------------------------------------------------------
q19_labels <- c(
"1" = "Very Poor",
"2" = "Poor",
"3" = "Unsure",
"4" = "Good",
"5" = "Excellent",
"6" = "Not Applicable"
)
# ------------------------------------------------------------------
# Q20: Aware of Racial Equity Policies
# ------------------------------------------------------------------
q20_labels <- c(
"1" = "Yes",
"2" = "No",
"3" = "Not Sure"
)
# ------------------------------------------------------------------
# Q22: How Came to Pittsburgh
# ------------------------------------------------------------------
q22_labels <- c(
"1" = "Born in Pittsburgh region",
"2" = "Not born but raised in Pittsburgh",
"3" = "Transplant for professional/employment",
"4" = "Transplant for educational opportunity",
"5" = "Transplant for loved one/family/friend",
"6" = "Prefer to self-describe"
)
# ------------------------------------------------------------------
# Q23: Current Residence Status
# ------------------------------------------------------------------
q23_labels <- c(
"1" = "Yes, no desire to leave",
"2" = "No, not currently, no plan to return",
"3" = "Yes, but strong desire to leave",
"4" = "Yes, after moving back (returnee)",
"5" = "No, but moving back in next 6-12 months"
)
# ------------------------------------------------------------------
# Q18: Institution Labels
# ------------------------------------------------------------------
q18_institution_labels <- c(
"Q18_1" = "Banks/Credit Unions",
"Q18_2" = "Charities/Nonprofits",
"Q18_3" = "Community Gardens",
"Q18_4" = "Cultural Centers",
"Q18_5" = "Faith-based Organizations",
"Q18_6" = "Fire Departments",
"Q18_7" = "Healthcare/Hospitals",
"Q18_8" = "K-12 Private Schools",
"Q18_9" = "K-12 Public Schools",
"Q18_10" = "Law Enforcement/Police",
"Q18_11" = "Legal Systems/Courts",
"Q18_12" = "Libraries",
"Q18_13" = "Local Government",
"Q18_14" = "Media/News Outlets",
"Q18_15" = "Parks/Recreation",
"Q18_16" = "Public Transit",
"Q18_17" = "Real Estate/Housing",
"Q18_18" = "Small Businesses",
"Q18_19" = "Social Services",
"Q18_20" = "Universities/Colleges",
"Q18_21" = "Utilities"
)
Helper Functions
# ------------------------------------------------------------------
# Frequency table for a single categorical variable
# ------------------------------------------------------------------
freq_table <- function(data, var, labels_vec, var_label = NULL) {
var_sym <- rlang::sym(var)
tbl <- data %>%
filter(!is.na(!!var_sym)) %>%
count(Value = as.character(!!var_sym)) %>%
mutate(
Label = labels_vec[Value],
Label = ifelse(is.na(Label), paste0("Code ", Value), Label),
Valid_Pct = round(n / sum(n) * 100, 1),
Cumulative_Pct = round(cumsum(n) / sum(n) * 100, 1)
) %>%
select(Value, Label, Count = n, `Valid %` = Valid_Pct, `Cumulative %` = Cumulative_Pct)
n_valid <- sum(!is.na(data[[var]]))
n_missing <- sum(is.na(data[[var]]))
if (!is.null(var_label)) {
cat("\n**", var_label, "** (", var, ")\n", sep = "")
}
cat("n =", n_valid, "| Missing =", n_missing,
"(", round(n_missing / nrow(data) * 100, 1), "%)\n\n")
tbl
}
# ------------------------------------------------------------------
# Bar chart for a categorical variable
# ------------------------------------------------------------------
freq_barplot <- function(data, var, labels_vec, title = "", wrap_width = 25) {
var_sym <- rlang::sym(var)
plot_df <- data %>%
filter(!is.na(!!var_sym)) %>%
mutate(
val = as.character(!!var_sym),
lab = labels_vec[val],
lab = ifelse(is.na(lab), paste0("Code ", val), lab),
lab = stringr::str_wrap(lab, width = wrap_width)
) %>%
count(lab) %>%
mutate(pct = n / sum(n) * 100)
ggplot(plot_df, aes(x = reorder(lab, n), y = pct)) +
geom_col(fill = "#2F5496", width = 0.7) +
geom_text(aes(label = paste0(round(pct, 1), "%")),
hjust = -0.1, size = 3) +
coord_flip() +
scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
labs(title = title, x = NULL, y = "Percent of Valid Responses") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))
}
Data Quality
Overview
missingness <- data.frame(
Column = names(df),
Dtype = sapply(df, class),
n_Valid = sapply(df, function(x) sum(!is.na(x))),
n_Missing = sapply(df, function(x) sum(is.na(x))),
Pct_Missing = round(sapply(df, function(x) mean(is.na(x))) * 100, 1),
Unique = sapply(df, function(x) length(unique(na.omit(x)))),
stringsAsFactors = FALSE, row.names = NULL
)
cat("Missingness Tiers:\n")
## Missingness Tiers:
cat(" Low (<10%): ", sum(missingness$Pct_Missing < 10), "columns\n")
## Low (<10%): 40 columns
cat(" Moderate (10-50%):", sum(missingness$Pct_Missing >= 10 & missingness$Pct_Missing < 50), "columns\n")
## Moderate (10-50%): 11 columns
cat(" High (50-90%): ", sum(missingness$Pct_Missing >= 50 & missingness$Pct_Missing < 90), "columns\n")
## High (50-90%): 34 columns
cat(" Very High (>90%): ", sum(missingness$Pct_Missing >= 90), "columns\n")
## Very High (>90%): 183 columns
core_vars <- c("Q2","Q3","Q4","Q5","Q6","Q7","Q8","Q9","Q10","Q12",
"Q13","Q14","Q16","Q17","Q19","Q20","Q22","Q23",
paste0("Q18_", 1:21))
missingness %>%
filter(Column %in% core_vars) %>%
arrange(Pct_Missing) %>%
kable(caption = "Missingness for Core Variables") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE)
Missingness for Core Variables
|
Column
|
Dtype
|
n_Valid
|
n_Missing
|
Pct_Missing
|
Unique
|
|
Q2
|
numeric
|
1471
|
0
|
0.0
|
6
|
|
Q6
|
numeric
|
1460
|
11
|
0.7
|
7
|
|
Q7
|
numeric
|
1461
|
10
|
0.7
|
9
|
|
Q4
|
numeric
|
1459
|
12
|
0.8
|
6
|
|
Q5
|
numeric
|
1458
|
13
|
0.9
|
9
|
|
Q9
|
numeric
|
1458
|
13
|
0.9
|
10
|
|
Q8
|
numeric
|
1454
|
17
|
1.2
|
5
|
|
Q3
|
numeric
|
1445
|
26
|
1.8
|
3
|
|
Q17
|
numeric
|
1435
|
36
|
2.4
|
7
|
|
Q20
|
numeric
|
1435
|
36
|
2.4
|
3
|
|
Q14
|
numeric
|
1432
|
39
|
2.7
|
5
|
|
Q16
|
numeric
|
1431
|
40
|
2.7
|
5
|
|
Q18_3
|
numeric
|
1431
|
40
|
2.7
|
5
|
|
Q12
|
numeric
|
1430
|
41
|
2.8
|
10
|
|
Q13
|
numeric
|
1428
|
43
|
2.9
|
5
|
|
Q18_1
|
numeric
|
1429
|
42
|
2.9
|
5
|
|
Q18_4
|
numeric
|
1428
|
43
|
2.9
|
5
|
|
Q18_5
|
numeric
|
1428
|
43
|
2.9
|
5
|
|
Q18_7
|
numeric
|
1429
|
42
|
2.9
|
5
|
|
Q18_6
|
numeric
|
1426
|
45
|
3.1
|
5
|
|
Q18_9
|
numeric
|
1426
|
45
|
3.1
|
5
|
|
Q18_11
|
numeric
|
1426
|
45
|
3.1
|
5
|
|
Q18_8
|
numeric
|
1424
|
47
|
3.2
|
5
|
|
Q18_2
|
numeric
|
1423
|
48
|
3.3
|
5
|
|
Q18_12
|
numeric
|
1422
|
49
|
3.3
|
5
|
|
Q18_13
|
numeric
|
1422
|
49
|
3.3
|
5
|
|
Q18_17
|
numeric
|
1422
|
49
|
3.3
|
5
|
|
Q18_19
|
numeric
|
1423
|
48
|
3.3
|
5
|
|
Q18_20
|
numeric
|
1423
|
48
|
3.3
|
5
|
|
Q18_14
|
numeric
|
1421
|
50
|
3.4
|
5
|
|
Q18_18
|
numeric
|
1421
|
50
|
3.4
|
5
|
|
Q18_10
|
numeric
|
1420
|
51
|
3.5
|
5
|
|
Q18_15
|
numeric
|
1420
|
51
|
3.5
|
5
|
|
Q18_21
|
numeric
|
1420
|
51
|
3.5
|
5
|
|
Q18_16
|
numeric
|
1417
|
54
|
3.7
|
5
|
|
Q23
|
numeric
|
1372
|
99
|
6.7
|
5
|
|
Q22
|
numeric
|
1366
|
105
|
7.1
|
6
|
|
Q10
|
numeric
|
1284
|
187
|
12.7
|
5
|
|
Q19
|
numeric
|
1255
|
216
|
14.7
|
5
|
Demographic Frequency
Tables
Racial Identity
(Q2)
freq_table(df, "Q2", q2_labels, "Racial Identity") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Racial Identity** (Q2)
## n = 1471 | Missing = 0 ( 0 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
10
|
Afro-Caribbean
|
17
|
1.2
|
1.2
|
|
3
|
Black/African American
|
1346
|
91.5
|
92.7
|
|
6
|
Biracial or Multiracial (Black and another race)
|
69
|
4.7
|
97.3
|
|
7
|
Prefer to self-describe
|
3
|
0.2
|
97.6
|
|
8
|
African
|
31
|
2.1
|
99.7
|
|
9
|
Afro-Latina/o/x
|
5
|
0.3
|
100.0
|
freq_barplot(df, "Q2", q2_labels, "Racial Identity (Q2)")

Ethnicity (Q3)
freq_table(df, "Q3", q3_labels, "Ethnicity") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Ethnicity** (Q3)
## n = 1445 | Missing = 26 ( 1.8 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Hispanic or Latina/o/x
|
235
|
16.3
|
16.3
|
|
2
|
Non-Hispanic or Latina/o/x
|
1167
|
80.8
|
97.0
|
|
3
|
Prefer not to identify
|
43
|
3.0
|
100.0
|
freq_barplot(df, "Q3", q3_labels, "Ethnicity (Q3)")

Gender (Q4)
freq_table(df, "Q4", q4_labels, "Gender") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Gender** (Q4)
## n = 1459 | Missing = 12 ( 0.8 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Male
|
870
|
59.6
|
59.6
|
|
2
|
Female
|
575
|
39.4
|
99.0
|
|
3
|
Non-binary / third gender
|
6
|
0.4
|
99.5
|
|
4
|
Transgender
|
4
|
0.3
|
99.7
|
|
5
|
Prefer to self-describe
|
2
|
0.1
|
99.9
|
|
6
|
Prefer not to respond
|
2
|
0.1
|
100.0
|
freq_barplot(df, "Q4", q4_labels, "Gender (Q4)")

Sexual Orientation
(Q5)
freq_table(df, "Q5", q5_labels, "Sexual Orientation") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Sexual Orientation** (Q5)
## n = 1458 | Missing = 13 ( 0.9 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Asexual
|
84
|
5.8
|
5.8
|
|
2
|
Bisexual
|
101
|
6.9
|
12.7
|
|
3
|
Pansexual
|
20
|
1.4
|
14.1
|
|
4
|
Gay
|
40
|
2.7
|
16.8
|
|
5
|
Lesbian
|
20
|
1.4
|
18.2
|
|
6
|
Heterosexual or straight
|
1163
|
79.8
|
97.9
|
|
7
|
Queer
|
13
|
0.9
|
98.8
|
|
8
|
Prefer to self-describe
|
4
|
0.3
|
99.1
|
|
9
|
Prefer not to respond
|
13
|
0.9
|
100.0
|
freq_barplot(df, "Q5", q5_labels, "Sexual Orientation (Q5)")

Age Group (Q6)
freq_table(df, "Q6", q6_labels, "Age Group") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Age Group** (Q6)
## n = 1460 | Missing = 11 ( 0.7 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
18-21 years old
|
53
|
3.6
|
3.6
|
|
2
|
21-34 years old
|
647
|
44.3
|
47.9
|
|
3
|
35-44 years old
|
490
|
33.6
|
81.5
|
|
4
|
45-54 years old
|
151
|
10.3
|
91.8
|
|
5
|
55-64 years old
|
72
|
4.9
|
96.8
|
|
6
|
65-74 years old
|
34
|
2.3
|
99.1
|
|
7
|
75 years or older
|
13
|
0.9
|
100.0
|
freq_barplot(df, "Q6", q6_labels, "Age Group (Q6)")

Highest Education
(Q7)
freq_table(df, "Q7", q7_labels, "Highest Education") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Highest Education** (Q7)
## n = 1461 | Missing = 10 ( 0.7 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Less than high school diploma
|
29
|
2.0
|
2.0
|
|
2
|
High school diploma or GED
|
109
|
7.5
|
9.4
|
|
3
|
Some college, but no degree
|
156
|
10.7
|
20.1
|
|
4
|
Trade/technical/vocational training
|
89
|
6.1
|
26.2
|
|
5
|
Associate degree
|
159
|
10.9
|
37.1
|
|
6
|
Bachelor’s degree
|
516
|
35.3
|
72.4
|
|
7
|
Master’s degree
|
295
|
20.2
|
92.6
|
|
8
|
Professional degree (MD, DDS, JD)
|
50
|
3.4
|
96.0
|
|
9
|
Doctorate degree (PhD, EdD)
|
58
|
4.0
|
100.0
|
freq_barplot(df, "Q7", q7_labels, "Highest Education (Q7)")

Marital Status
(Q8)
freq_table(df, "Q8", q8_labels, "Marital Status") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Marital Status** (Q8)
## n = 1454 | Missing = 17 ( 1.2 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Single, never married
|
445
|
30.6
|
30.6
|
|
2
|
Married or domestic partnership
|
878
|
60.4
|
91.0
|
|
3
|
Widowed
|
35
|
2.4
|
93.4
|
|
4
|
Divorced
|
71
|
4.9
|
98.3
|
|
5
|
Separated
|
25
|
1.7
|
100.0
|
freq_barplot(df, "Q8", q8_labels, "Marital Status (Q8)")

Employment Status
(Q9)
freq_table(df, "Q9", q9_labels, "Employment Status") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Employment Status** (Q9)
## n = 1458 | Missing = 13 ( 0.9 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Employed full-time
|
960
|
65.8
|
65.8
|
|
10
|
Unable to work
|
4
|
0.3
|
66.1
|
|
2
|
Employed part-time
|
197
|
13.5
|
79.6
|
|
3
|
Self-employed
|
148
|
10.2
|
89.8
|
|
4
|
Out of work, looking
|
40
|
2.7
|
92.5
|
|
5
|
Out of work, not looking
|
10
|
0.7
|
93.2
|
|
6
|
Homemaker
|
11
|
0.8
|
94.0
|
|
7
|
Student
|
48
|
3.3
|
97.3
|
|
8
|
Military
|
3
|
0.2
|
97.5
|
|
9
|
Retired
|
37
|
2.5
|
100.0
|
freq_barplot(df, "Q9", q9_labels, "Employment Status (Q9)")

Work Location
(Q10)
freq_table(df, "Q10", q10_labels, "Work Location") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Work Location** (Q10)
## n = 1284 | Missing = 187 ( 12.7 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
100% virtual
|
125
|
9.7
|
9.7
|
|
2
|
Mostly virtual
|
182
|
14.2
|
23.9
|
|
3
|
Half virtual / half physical
|
291
|
22.7
|
46.6
|
|
4
|
Mostly physical location
|
338
|
26.3
|
72.9
|
|
5
|
100% physical location
|
348
|
27.1
|
100.0
|
freq_barplot(df, "Q10", q10_labels, "Work Location (Q10)")

Household Income
(Q12)
freq_table(df, "Q12", q12_labels, "Household Income") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Household Income** (Q12)
## n = 1430 | Missing = 41 ( 2.8 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Less than $10,000
|
69
|
4.8
|
4.8
|
|
10
|
$200,000 or more
|
110
|
7.7
|
12.5
|
|
2
|
$10,000 to $14,999
|
67
|
4.7
|
17.2
|
|
3
|
$15,000 to $24,999
|
88
|
6.2
|
23.4
|
|
4
|
$25,000 to $34,999
|
105
|
7.3
|
30.7
|
|
5
|
$35,000 to $49,999
|
160
|
11.2
|
41.9
|
|
6
|
$50,000 to $74,999
|
260
|
18.2
|
60.1
|
|
7
|
$75,000 to $99,999
|
238
|
16.6
|
76.7
|
|
8
|
$100,000 to $149,999
|
237
|
16.6
|
93.3
|
|
9
|
$150,000 to $199,999
|
96
|
6.7
|
100.0
|
freq_barplot(df, "Q12", q12_labels, "Household Income (Q12)")

How Came to
Pittsburgh (Q22)
freq_table(df, "Q22", q22_labels, "How Came to Pittsburgh") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **How Came to Pittsburgh** (Q22)
## n = 1366 | Missing = 105 ( 7.1 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Born in Pittsburgh region
|
767
|
56.1
|
56.1
|
|
2
|
Not born but raised in Pittsburgh
|
220
|
16.1
|
72.3
|
|
3
|
Transplant for professional/employment
|
191
|
14.0
|
86.2
|
|
4
|
Transplant for educational opportunity
|
93
|
6.8
|
93.0
|
|
5
|
Transplant for loved one/family/friend
|
72
|
5.3
|
98.3
|
|
6
|
Prefer to self-describe
|
23
|
1.7
|
100.0
|
freq_barplot(df, "Q22", q22_labels, "How Came to Pittsburgh (Q22)")

Current Residence
Status (Q23)
freq_table(df, "Q23", q23_labels, "Current Residence Status") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Current Residence Status** (Q23)
## n = 1372 | Missing = 99 ( 6.7 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Yes, no desire to leave
|
858
|
62.5
|
62.5
|
|
2
|
No, not currently, no plan to return
|
158
|
11.5
|
74.1
|
|
3
|
Yes, but strong desire to leave
|
210
|
15.3
|
89.4
|
|
4
|
Yes, after moving back (returnee)
|
71
|
5.2
|
94.5
|
|
5
|
No, but moving back in next 6-12 months
|
75
|
5.5
|
100.0
|
freq_barplot(df, "Q23", q23_labels, "Current Residence Status (Q23)")

Institution Experience
Ratings (Q18)
Q18 asked respondents to rate their overall experience with 21 local
institutions. The scale runs from 1 (lowest) to 6 (highest).
Summary
Statistics
q18_cols <- grep("^Q18_\\d+$", names(df), value = TRUE)
q18_summary <- data.frame(
Variable = q18_cols,
Institution = q18_institution_labels[q18_cols],
stringsAsFactors = FALSE
)
q18_stats <- df %>%
select(all_of(q18_cols)) %>%
psych::describe() %>%
as.data.frame() %>%
mutate(Variable = q18_cols) %>%
select(Variable, n, mean, sd, median, min, max, range, skew, kurtosis)
q18_summary <- q18_summary %>%
left_join(q18_stats, by = "Variable") %>%
mutate(
IQR = sapply(q18_cols, function(x) IQR(df[[x]], na.rm = TRUE)),
Q1 = sapply(q18_cols, function(x) quantile(df[[x]], 0.25, na.rm = TRUE)),
Q3 = sapply(q18_cols, function(x) quantile(df[[x]], 0.75, na.rm = TRUE)),
Pct_Low = sapply(q18_cols, function(x) round(mean(df[[x]] %in% c(1, 2), na.rm = TRUE) * 100, 1)),
Pct_High = sapply(q18_cols, function(x) round(mean(df[[x]] %in% c(5, 6), na.rm = TRUE) * 100, 1))
) %>%
arrange(mean)
q18_summary %>%
mutate(across(where(is.numeric) & !c(n), ~ round(.x, 3))) %>%
kable(caption = "Q18 Institution Ratings – Descriptive Statistics (sorted by mean, ascending)") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE, font_size = 12) %>%
scroll_box(width = "100%")
Q18 Institution Ratings – Descriptive Statistics (sorted by mean,
ascending)
|
Variable
|
Institution
|
n
|
mean
|
sd
|
median
|
min
|
max
|
range
|
skew
|
kurtosis
|
IQR
|
Q1
|
Q3
|
Pct_Low
|
Pct_High
|
|
Q18_7
|
Healthcare/Hospitals
|
1429
|
4.430
|
1.270
|
5
|
1
|
6
|
5
|
-0.976
|
0.650
|
1.00
|
4
|
5.00
|
5.4
|
57.9
|
|
Q18_20
|
Universities/Colleges
|
1423
|
4.455
|
1.253
|
5
|
1
|
6
|
5
|
-1.009
|
0.881
|
1.00
|
4
|
5.00
|
5.3
|
56.2
|
|
Q18_5
|
Faith-based Organizations
|
1428
|
4.509
|
1.231
|
5
|
1
|
6
|
5
|
-1.051
|
1.001
|
1.00
|
4
|
5.00
|
4.8
|
59.2
|
|
Q18_10
|
Law Enforcement/Police
|
1420
|
4.529
|
1.242
|
5
|
1
|
6
|
5
|
-0.994
|
0.850
|
1.00
|
4
|
5.00
|
4.6
|
58.2
|
|
Q18_16
|
Public Transit
|
1417
|
4.535
|
1.318
|
5
|
1
|
6
|
5
|
-1.098
|
0.777
|
1.00
|
4
|
5.00
|
5.8
|
62.4
|
|
Q18_17
|
Real Estate/Housing
|
1422
|
4.563
|
1.139
|
5
|
1
|
6
|
5
|
-0.957
|
1.197
|
1.00
|
4
|
5.00
|
3.3
|
56.7
|
|
Q18_19
|
Social Services
|
1423
|
4.591
|
1.119
|
5
|
1
|
6
|
5
|
-1.026
|
1.413
|
1.00
|
4
|
5.00
|
3.1
|
58.9
|
|
Q18_12
|
Libraries
|
1422
|
4.605
|
1.216
|
5
|
1
|
6
|
5
|
-1.172
|
1.281
|
1.00
|
4
|
5.00
|
4.4
|
64.2
|
|
Q18_6
|
Fire Departments
|
1426
|
4.623
|
1.115
|
5
|
1
|
6
|
5
|
-1.029
|
1.549
|
1.00
|
4
|
5.00
|
3.1
|
58.3
|
|
Q18_2
|
Charities/Nonprofits
|
1423
|
4.625
|
1.153
|
5
|
1
|
6
|
5
|
-1.081
|
1.317
|
1.00
|
4
|
5.00
|
3.3
|
62.1
|
|
Q18_13
|
Local Government
|
1422
|
4.634
|
1.156
|
5
|
1
|
6
|
5
|
-1.084
|
1.359
|
1.00
|
4
|
5.00
|
3.4
|
61.6
|
|
Q18_14
|
Media/News Outlets
|
1421
|
4.657
|
1.186
|
5
|
1
|
6
|
5
|
-1.215
|
1.489
|
1.00
|
4
|
5.00
|
3.9
|
66.0
|
|
Q18_18
|
Small Businesses
|
1421
|
4.676
|
1.102
|
5
|
1
|
6
|
5
|
-1.103
|
1.687
|
1.00
|
4
|
5.00
|
2.9
|
62.0
|
|
Q18_3
|
Community Gardens
|
1431
|
4.688
|
1.217
|
5
|
1
|
6
|
5
|
-1.265
|
1.479
|
1.00
|
4
|
5.00
|
4.2
|
68.7
|
|
Q18_21
|
Utilities
|
1420
|
4.688
|
1.143
|
5
|
1
|
6
|
5
|
-1.197
|
1.731
|
1.00
|
4
|
5.00
|
3.4
|
64.2
|
|
Q18_1
|
Banks/Credit Unions
|
1429
|
4.709
|
1.164
|
5
|
1
|
6
|
5
|
-1.413
|
2.116
|
1.00
|
4
|
5.00
|
4.0
|
70.8
|
|
Q18_4
|
Cultural Centers
|
1428
|
4.727
|
1.153
|
5
|
1
|
6
|
5
|
-1.335
|
1.922
|
1.00
|
4
|
5.00
|
3.5
|
70.1
|
|
Q18_9
|
K-12 Public Schools
|
1426
|
4.774
|
1.157
|
5
|
1
|
6
|
5
|
-1.483
|
2.400
|
1.00
|
4
|
5.00
|
3.9
|
72.6
|
|
Q18_8
|
K-12 Private Schools
|
1424
|
4.778
|
1.092
|
5
|
1
|
6
|
5
|
-1.227
|
1.882
|
1.25
|
4
|
5.25
|
2.5
|
68.7
|
|
Q18_11
|
Legal Systems/Courts
|
1426
|
4.814
|
1.144
|
5
|
1
|
6
|
5
|
-1.405
|
2.215
|
2.00
|
4
|
6.00
|
3.3
|
72.0
|
|
Q18_15
|
Parks/Recreation
|
1420
|
4.850
|
1.117
|
5
|
1
|
6
|
5
|
-1.503
|
2.726
|
1.00
|
5
|
6.00
|
3.3
|
72.9
|
Mean Ratings –
Horizontal Bar Chart
q18_summary %>%
ggplot(aes(x = reorder(Institution, mean), y = mean)) +
geom_col(fill = "#2F5496", width = 0.7) +
geom_errorbar(aes(ymin = mean - sd, ymax = mean + sd), width = 0.3, color = "gray40") +
geom_text(aes(label = round(mean, 2)), hjust = -0.3, size = 3) +
coord_flip() +
scale_y_continuous(limits = c(0, 7), breaks = 1:6) +
labs(title = "Q18: Mean Institution Ratings (with ±1 SD)",
x = NULL, y = "Mean Rating (1-6 scale)") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))

Full Frequency
Distributions
q18_freq <- df %>%
select(all_of(q18_cols)) %>%
pivot_longer(everything(), names_to = "Variable", values_to = "Rating") %>%
filter(!is.na(Rating)) %>%
mutate(Institution = q18_institution_labels[Variable]) %>%
count(Institution, Rating) %>%
group_by(Institution) %>%
mutate(Pct = round(n / sum(n) * 100, 1)) %>%
ungroup()
q18_freq %>%
select(Institution, Rating, Count = n, `Valid %` = Pct) %>%
kable(caption = "Q18 Institution Ratings – Full Frequency Distributions") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE) %>%
collapse_rows(columns = 1, valign = "top")
Q18 Institution Ratings – Full Frequency Distributions
|
Institution
|
Rating
|
Count
|
Valid %
|
|
Banks/Credit Unions
|
1
|
59
|
4.1
|
|
3
|
159
|
11.1
|
|
4
|
169
|
11.8
|
|
5
|
735
|
51.4
|
|
6
|
307
|
21.5
|
|
Charities/Nonprofits
|
1
|
49
|
3.4
|
|
3
|
181
|
12.7
|
|
4
|
279
|
19.6
|
|
5
|
610
|
42.9
|
|
6
|
304
|
21.4
|
|
Community Gardens
|
1
|
62
|
4.3
|
|
3
|
187
|
13.1
|
|
4
|
171
|
11.9
|
|
5
|
665
|
46.5
|
|
6
|
346
|
24.2
|
|
Cultural Centers
|
1
|
52
|
3.6
|
|
3
|
166
|
11.6
|
|
4
|
179
|
12.5
|
|
5
|
702
|
49.2
|
|
6
|
329
|
23.0
|
|
Faith-based Organizations
|
1
|
70
|
4.9
|
|
3
|
208
|
14.6
|
|
4
|
279
|
19.5
|
|
5
|
597
|
41.8
|
|
6
|
274
|
19.2
|
|
Fire Departments
|
1
|
46
|
3.2
|
|
3
|
137
|
9.6
|
|
4
|
386
|
27.1
|
|
5
|
551
|
38.6
|
|
6
|
306
|
21.5
|
|
Healthcare/Hospitals
|
1
|
79
|
5.5
|
|
3
|
252
|
17.6
|
|
4
|
246
|
17.2
|
|
5
|
601
|
42.1
|
|
6
|
251
|
17.6
|
|
K-12 Private Schools
|
1
|
37
|
2.6
|
|
3
|
148
|
10.4
|
|
4
|
228
|
16.0
|
|
5
|
655
|
46.0
|
|
6
|
356
|
25.0
|
|
K-12 Public Schools
|
1
|
57
|
4.0
|
|
3
|
140
|
9.8
|
|
4
|
161
|
11.3
|
|
5
|
721
|
50.6
|
|
6
|
347
|
24.3
|
|
Law Enforcement/Police
|
1
|
67
|
4.7
|
|
3
|
210
|
14.8
|
|
4
|
287
|
20.2
|
|
5
|
550
|
38.7
|
|
6
|
306
|
21.5
|
|
Legal Systems/Courts
|
1
|
49
|
3.4
|
|
3
|
141
|
9.9
|
|
4
|
177
|
12.4
|
|
5
|
669
|
46.9
|
|
6
|
390
|
27.3
|
|
Libraries
|
1
|
64
|
4.5
|
|
3
|
194
|
13.6
|
|
4
|
219
|
15.4
|
|
5
|
643
|
45.2
|
|
6
|
302
|
21.2
|
|
Local Government
|
1
|
50
|
3.5
|
|
3
|
170
|
12.0
|
|
4
|
296
|
20.8
|
|
5
|
591
|
41.6
|
|
6
|
315
|
22.2
|
|
Media/News Outlets
|
1
|
57
|
4.0
|
|
3
|
182
|
12.8
|
|
4
|
211
|
14.8
|
|
5
|
656
|
46.2
|
|
6
|
315
|
22.2
|
|
Parks/Recreation
|
1
|
48
|
3.4
|
|
3
|
114
|
8.0
|
|
4
|
186
|
13.1
|
|
5
|
679
|
47.8
|
|
6
|
393
|
27.7
|
|
Public Transit
|
1
|
86
|
6.1
|
|
3
|
220
|
15.5
|
|
4
|
193
|
13.6
|
|
5
|
600
|
42.3
|
|
6
|
318
|
22.4
|
|
Real Estate/Housing
|
1
|
48
|
3.4
|
|
3
|
174
|
12.2
|
|
4
|
366
|
25.7
|
|
5
|
549
|
38.6
|
|
6
|
285
|
20.0
|
|
Small Businesses
|
1
|
42
|
3.0
|
|
3
|
140
|
9.9
|
|
4
|
327
|
23.0
|
|
5
|
598
|
42.1
|
|
6
|
314
|
22.1
|
|
Social Services
|
1
|
46
|
3.2
|
|
3
|
165
|
11.6
|
|
4
|
346
|
24.3
|
|
5
|
588
|
41.3
|
|
6
|
278
|
19.5
|
|
Universities/Colleges
|
1
|
78
|
5.5
|
|
3
|
208
|
14.6
|
|
4
|
311
|
21.9
|
|
5
|
562
|
39.5
|
|
6
|
264
|
18.6
|
|
Utilities
|
1
|
50
|
3.5
|
|
3
|
146
|
10.3
|
|
4
|
280
|
19.7
|
|
5
|
615
|
43.3
|
|
6
|
329
|
23.2
|
Heatmap of Rating
Distributions
q18_freq %>%
mutate(Institution = factor(Institution,
levels = q18_summary$Institution)) %>%
ggplot(aes(x = factor(Rating), y = Institution, fill = Pct)) +
geom_tile(color = "white") +
geom_text(aes(label = paste0(Pct, "%")), size = 3) +
scale_fill_gradient(low = "#F7FBFF", high = "#2F5496", name = "% of\nResponses") +
labs(title = "Q18: Distribution of Ratings by Institution",
x = "Rating", y = NULL) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))

Opinion and
Satisfaction Scales
Professional
Development Rating (Q13)
freq_table(df, "Q13", q13_labels, "Professional Development/Career Resources Rating") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Professional Development/Career Resources Rating** (Q13)
## n = 1428 | Missing = 43 ( 2.9 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Very Poor
|
84
|
5.9
|
5.9
|
|
2
|
Poor
|
245
|
17.2
|
23.0
|
|
3
|
Unsure or not applicable
|
183
|
12.8
|
35.9
|
|
4
|
Good
|
687
|
48.1
|
84.0
|
|
5
|
Excellent
|
229
|
16.0
|
100.0
|
freq_barplot(df, "Q13", q13_labels, "Professional Development Rating (Q13)")

Hear About Events –
Frequency (Q14)
freq_table(df, "Q14", q14_labels, "How Often Hear About Events") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **How Often Hear About Events** (Q14)
## n = 1432 | Missing = 39 ( 2.7 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Never
|
28
|
2.0
|
2.0
|
|
2
|
Rarely
|
186
|
13.0
|
14.9
|
|
3
|
Occasionally
|
531
|
37.1
|
52.0
|
|
4
|
Frequently
|
518
|
36.2
|
88.2
|
|
5
|
Very Frequently
|
169
|
11.8
|
100.0
|
freq_barplot(df, "Q14", q14_labels, "Hear About Events (Q14)")

Event Participation
Frequency (Q16)
freq_table(df, "Q16", q16_labels, "Event Participation Frequency") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Event Participation Frequency** (Q16)
## n = 1431 | Missing = 40 ( 2.7 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Never
|
44
|
3.1
|
3.1
|
|
2
|
Rarely
|
242
|
16.9
|
20.0
|
|
3
|
Occasionally
|
557
|
38.9
|
58.9
|
|
4
|
Frequently
|
445
|
31.1
|
90.0
|
|
5
|
Very Frequently
|
143
|
10.0
|
100.0
|
freq_barplot(df, "Q16", q16_labels, "Event Participation Frequency (Q16)")

Satisfaction with
Cultural/Social Events (Q17)
freq_table(df, "Q17", q17_labels, "Satisfaction with Cultural/Social Events") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Satisfaction with Cultural/Social Events** (Q17)
## n = 1435 | Missing = 36 ( 2.4 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Highly dissatisfied
|
57
|
4.0
|
4.0
|
|
2
|
Dissatisfied
|
108
|
7.5
|
11.5
|
|
3
|
Somewhat dissatisfied
|
189
|
13.2
|
24.7
|
|
4
|
Neither satisfied nor dissatisfied
|
149
|
10.4
|
35.1
|
|
5
|
Somewhat satisfied
|
339
|
23.6
|
58.7
|
|
6
|
Satisfied
|
426
|
29.7
|
88.4
|
|
7
|
Highly satisfied
|
167
|
11.6
|
100.0
|
freq_barplot(df, "Q17", q17_labels, "Satisfaction with Events (Q17)")

Entrepreneurship
Support Ecosystem (Q19)
freq_table(df, "Q19", q19_labels, "Entrepreneurship Support Ecosystem Rating") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Entrepreneurship Support Ecosystem Rating** (Q19)
## n = 1255 | Missing = 216 ( 14.7 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Very Poor
|
76
|
6.1
|
6.1
|
|
2
|
Poor
|
199
|
15.9
|
21.9
|
|
3
|
Unsure
|
156
|
12.4
|
34.3
|
|
4
|
Good
|
450
|
35.9
|
70.2
|
|
5
|
Excellent
|
374
|
29.8
|
100.0
|
freq_barplot(df, "Q19", q19_labels, "Entrepreneurship Support (Q19)")

Awareness of Racial
Equity Policies (Q20)
freq_table(df, "Q20", q20_labels, "Aware of Racial Equity Policies") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Aware of Racial Equity Policies** (Q20)
## n = 1435 | Missing = 36 ( 2.4 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Yes
|
654
|
45.6
|
45.6
|
|
2
|
No
|
602
|
42.0
|
87.5
|
|
3
|
Not Sure
|
179
|
12.5
|
100.0
|
freq_barplot(df, "Q20", q20_labels, "Aware of Racial Equity Policies (Q20)")

Summary Statistics
for All Opinion/Satisfaction Scales
opinion_vars <- c("Q13", "Q14", "Q16", "Q17", "Q19")
opinion_labels <- c(
"Q13" = "Professional Development Rating (1-5)",
"Q14" = "Hear About Events Frequency (1-5)",
"Q16" = "Event Participation Frequency (1-5)",
"Q17" = "Satisfaction with Events (1-7)",
"Q19" = "Entrepreneurship Support Rating (1-5/6)"
)
opinion_stats <- data.frame(
Variable = opinion_vars,
Label = opinion_labels[opinion_vars],
stringsAsFactors = FALSE
)
opinion_desc <- df %>%
select(all_of(opinion_vars)) %>%
psych::describe() %>%
as.data.frame() %>%
mutate(Variable = opinion_vars) %>%
select(Variable, n, mean, sd, median, min, max, skew, kurtosis)
opinion_stats <- opinion_stats %>%
left_join(opinion_desc, by = "Variable") %>%
mutate(across(where(is.numeric) & !c(n), ~ round(.x, 3)))
opinion_stats %>%
kable(caption = "Opinion/Satisfaction Scales – Descriptive Statistics") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
Opinion/Satisfaction Scales – Descriptive Statistics
|
Variable
|
Label
|
n
|
mean
|
sd
|
median
|
min
|
max
|
skew
|
kurtosis
|
|
Q13
|
Professional Development Rating (1-5)
|
1428
|
3.513
|
1.126
|
4
|
1
|
5
|
-0.673
|
-0.471
|
|
Q14
|
Hear About Events Frequency (1-5)
|
1432
|
3.429
|
0.927
|
3
|
1
|
5
|
-0.205
|
-0.298
|
|
Q16
|
Event Participation Frequency (1-5)
|
1431
|
3.280
|
0.962
|
3
|
1
|
5
|
-0.117
|
-0.375
|
|
Q17
|
Satisfaction with Events (1-7)
|
1435
|
4.778
|
1.630
|
5
|
1
|
7
|
-0.628
|
-0.534
|
|
Q19
|
Entrepreneurship Support Rating (1-5/6)
|
1255
|
3.675
|
1.224
|
4
|
1
|
5
|
-0.675
|
-0.637
|
Branched Scales
(Subgroup-Specific)
These variables are only shown to specific subgroups based on skip
logic, so sample sizes are much smaller.
branched_vars <- list(
Q21 = list(label = "Effectiveness of Racial Equity Policies (1-5)", codes = c("1"="Not effective at all","2"="Somewhat effective","3"="Unsure","4"="Somewhat effective","5"="Extremely effective")),
Q28 = list(label = "Likelihood of Staying Long-term 7-10yr (1-7)", codes = c("1"="Extremely unlikely","2"="Unlikely","3"="Slightly unlikely","4"="Neither","5"="Slightly likely","6"="Likely","7"="Extremely likely")),
Q30 = list(label = "Satisfaction with Life in Pittsburgh (1-7)", codes = c("1"="Highly dissatisfied","2"="Dissatisfied","3"="Somewhat dissatisfied","4"="Neither","5"="Somewhat satisfied","6"="Satisfied","7"="Highly satisfied")),
Q42 = list(label = "Satisfaction with Decision to Return (1-7)", codes = c("1"="Highly dissatisfied","2"="Dissatisfied","3"="Somewhat dissatisfied","4"="Neither","5"="Somewhat satisfied","6"="Satisfied","7"="Highly satisfied")),
Q67 = list(label = "Satisfaction with Decision to Leave (1-7)", codes = c("1"="Highly dissatisfied","2"="Dissatisfied","3"="Somewhat dissatisfied","4"="Neither","5"="Somewhat satisfied","6"="Satisfied","7"="Highly satisfied"))
)
for (var in names(branched_vars)) {
info <- branched_vars[[var]]
s <- df[[var]][!is.na(df[[var]])]
if (length(s) == 0) next
cat("\n## ", info$label, " (", var, ")\n\n", sep = "")
cat("n =", length(s), "| Missing =", sum(is.na(df[[var]])), "\n")
cat("Mean =", round(mean(s), 3), "| Median =", median(s),
"| SD =", round(sd(s), 3), "| Skew =", round(psych::skew(s), 3), "\n\n")
tbl <- freq_table(df, var, info$codes)
print(kable(tbl) %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE))
cat("\n")
}
##
## ## Effectiveness of Racial Equity Policies (1-5) (Q21)
##
## n = 787 | Missing = 684
## Mean = 3.574 | Median = 4 | SD = 1.184 | Skew = -0.547
##
## n = 787 | Missing = 684 ( 46.5 %)
##
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <thead>
## <tr>
## <th style="text-align:left;"> Value </th>
## <th style="text-align:left;"> Label </th>
## <th style="text-align:right;"> Count </th>
## <th style="text-align:right;"> Valid % </th>
## <th style="text-align:right;"> Cumulative % </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> 1 </td>
## <td style="text-align:left;"> Not effective at all </td>
## <td style="text-align:right;"> 43 </td>
## <td style="text-align:right;"> 5.5 </td>
## <td style="text-align:right;"> 5.5 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 2 </td>
## <td style="text-align:left;"> Somewhat effective </td>
## <td style="text-align:right;"> 136 </td>
## <td style="text-align:right;"> 17.3 </td>
## <td style="text-align:right;"> 22.7 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 3 </td>
## <td style="text-align:left;"> Unsure </td>
## <td style="text-align:right;"> 124 </td>
## <td style="text-align:right;"> 15.8 </td>
## <td style="text-align:right;"> 38.5 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 4 </td>
## <td style="text-align:left;"> Somewhat effective </td>
## <td style="text-align:right;"> 294 </td>
## <td style="text-align:right;"> 37.4 </td>
## <td style="text-align:right;"> 75.9 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 5 </td>
## <td style="text-align:left;"> Extremely effective </td>
## <td style="text-align:right;"> 190 </td>
## <td style="text-align:right;"> 24.1 </td>
## <td style="text-align:right;"> 100.0 </td>
## </tr>
## </tbody>
## </table>
##
## ## Likelihood of Staying Long-term 7-10yr (1-7) (Q28)
##
## n = 841 | Missing = 630
## Mean = 5.91 | Median = 6 | SD = 1.262 | Skew = -1.762
##
## n = 841 | Missing = 630 ( 42.8 %)
##
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <thead>
## <tr>
## <th style="text-align:left;"> Value </th>
## <th style="text-align:left;"> Label </th>
## <th style="text-align:right;"> Count </th>
## <th style="text-align:right;"> Valid % </th>
## <th style="text-align:right;"> Cumulative % </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> 1 </td>
## <td style="text-align:left;"> Extremely unlikely </td>
## <td style="text-align:right;"> 11 </td>
## <td style="text-align:right;"> 1.3 </td>
## <td style="text-align:right;"> 1.3 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 2 </td>
## <td style="text-align:left;"> Unlikely </td>
## <td style="text-align:right;"> 19 </td>
## <td style="text-align:right;"> 2.3 </td>
## <td style="text-align:right;"> 3.6 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 3 </td>
## <td style="text-align:left;"> Slightly unlikely </td>
## <td style="text-align:right;"> 27 </td>
## <td style="text-align:right;"> 3.2 </td>
## <td style="text-align:right;"> 6.8 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 4 </td>
## <td style="text-align:left;"> Neither </td>
## <td style="text-align:right;"> 26 </td>
## <td style="text-align:right;"> 3.1 </td>
## <td style="text-align:right;"> 9.9 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 5 </td>
## <td style="text-align:left;"> Slightly likely </td>
## <td style="text-align:right;"> 115 </td>
## <td style="text-align:right;"> 13.7 </td>
## <td style="text-align:right;"> 23.5 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 6 </td>
## <td style="text-align:left;"> Likely </td>
## <td style="text-align:right;"> 340 </td>
## <td style="text-align:right;"> 40.4 </td>
## <td style="text-align:right;"> 64.0 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 7 </td>
## <td style="text-align:left;"> Extremely likely </td>
## <td style="text-align:right;"> 303 </td>
## <td style="text-align:right;"> 36.0 </td>
## <td style="text-align:right;"> 100.0 </td>
## </tr>
## </tbody>
## </table>
##
## ## Satisfaction with Life in Pittsburgh (1-7) (Q30)
##
## n = 835 | Missing = 636
## Mean = 5.776 | Median = 6 | SD = 1.112 | Skew = -1.365
##
## n = 835 | Missing = 636 ( 43.2 %)
##
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <thead>
## <tr>
## <th style="text-align:left;"> Value </th>
## <th style="text-align:left;"> Label </th>
## <th style="text-align:right;"> Count </th>
## <th style="text-align:right;"> Valid % </th>
## <th style="text-align:right;"> Cumulative % </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> 1 </td>
## <td style="text-align:left;"> Highly dissatisfied </td>
## <td style="text-align:right;"> 5 </td>
## <td style="text-align:right;"> 0.6 </td>
## <td style="text-align:right;"> 0.6 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 2 </td>
## <td style="text-align:left;"> Dissatisfied </td>
## <td style="text-align:right;"> 7 </td>
## <td style="text-align:right;"> 0.8 </td>
## <td style="text-align:right;"> 1.4 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 3 </td>
## <td style="text-align:left;"> Somewhat dissatisfied </td>
## <td style="text-align:right;"> 35 </td>
## <td style="text-align:right;"> 4.2 </td>
## <td style="text-align:right;"> 5.6 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 4 </td>
## <td style="text-align:left;"> Neither </td>
## <td style="text-align:right;"> 37 </td>
## <td style="text-align:right;"> 4.4 </td>
## <td style="text-align:right;"> 10.1 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 5 </td>
## <td style="text-align:left;"> Somewhat satisfied </td>
## <td style="text-align:right;"> 164 </td>
## <td style="text-align:right;"> 19.6 </td>
## <td style="text-align:right;"> 29.7 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 6 </td>
## <td style="text-align:left;"> Satisfied </td>
## <td style="text-align:right;"> 378 </td>
## <td style="text-align:right;"> 45.3 </td>
## <td style="text-align:right;"> 75.0 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 7 </td>
## <td style="text-align:left;"> Highly satisfied </td>
## <td style="text-align:right;"> 209 </td>
## <td style="text-align:right;"> 25.0 </td>
## <td style="text-align:right;"> 100.0 </td>
## </tr>
## </tbody>
## </table>
##
## ## Satisfaction with Decision to Return (1-7) (Q42)
##
## n = 69 | Missing = 1402
## Mean = 4.942 | Median = 5 | SD = 1.765 | Skew = -0.689
##
## n = 69 | Missing = 1402 ( 95.3 %)
##
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <thead>
## <tr>
## <th style="text-align:left;"> Value </th>
## <th style="text-align:left;"> Label </th>
## <th style="text-align:right;"> Count </th>
## <th style="text-align:right;"> Valid % </th>
## <th style="text-align:right;"> Cumulative % </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> 1 </td>
## <td style="text-align:left;"> Highly dissatisfied </td>
## <td style="text-align:right;"> 4 </td>
## <td style="text-align:right;"> 5.8 </td>
## <td style="text-align:right;"> 5.8 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 2 </td>
## <td style="text-align:left;"> Dissatisfied </td>
## <td style="text-align:right;"> 4 </td>
## <td style="text-align:right;"> 5.8 </td>
## <td style="text-align:right;"> 11.6 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 3 </td>
## <td style="text-align:left;"> Somewhat dissatisfied </td>
## <td style="text-align:right;"> 7 </td>
## <td style="text-align:right;"> 10.1 </td>
## <td style="text-align:right;"> 21.7 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 4 </td>
## <td style="text-align:left;"> Neither </td>
## <td style="text-align:right;"> 9 </td>
## <td style="text-align:right;"> 13.0 </td>
## <td style="text-align:right;"> 34.8 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 5 </td>
## <td style="text-align:left;"> Somewhat satisfied </td>
## <td style="text-align:right;"> 12 </td>
## <td style="text-align:right;"> 17.4 </td>
## <td style="text-align:right;"> 52.2 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 6 </td>
## <td style="text-align:left;"> Satisfied </td>
## <td style="text-align:right;"> 19 </td>
## <td style="text-align:right;"> 27.5 </td>
## <td style="text-align:right;"> 79.7 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 7 </td>
## <td style="text-align:left;"> Highly satisfied </td>
## <td style="text-align:right;"> 14 </td>
## <td style="text-align:right;"> 20.3 </td>
## <td style="text-align:right;"> 100.0 </td>
## </tr>
## </tbody>
## </table>
##
## ## Satisfaction with Decision to Leave (1-7) (Q67)
##
## n = 154 | Missing = 1317
## Mean = 5.312 | Median = 6 | SD = 1.582 | Skew = -0.919
##
## n = 154 | Missing = 1317 ( 89.5 %)
##
## <table class="table table-striped table-hover table-condensed" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <thead>
## <tr>
## <th style="text-align:left;"> Value </th>
## <th style="text-align:left;"> Label </th>
## <th style="text-align:right;"> Count </th>
## <th style="text-align:right;"> Valid % </th>
## <th style="text-align:right;"> Cumulative % </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> 1 </td>
## <td style="text-align:left;"> Highly dissatisfied </td>
## <td style="text-align:right;"> 5 </td>
## <td style="text-align:right;"> 3.2 </td>
## <td style="text-align:right;"> 3.2 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 2 </td>
## <td style="text-align:left;"> Dissatisfied </td>
## <td style="text-align:right;"> 6 </td>
## <td style="text-align:right;"> 3.9 </td>
## <td style="text-align:right;"> 7.1 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 3 </td>
## <td style="text-align:left;"> Somewhat dissatisfied </td>
## <td style="text-align:right;"> 9 </td>
## <td style="text-align:right;"> 5.8 </td>
## <td style="text-align:right;"> 13.0 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 4 </td>
## <td style="text-align:left;"> Neither </td>
## <td style="text-align:right;"> 20 </td>
## <td style="text-align:right;"> 13.0 </td>
## <td style="text-align:right;"> 26.0 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 5 </td>
## <td style="text-align:left;"> Somewhat satisfied </td>
## <td style="text-align:right;"> 32 </td>
## <td style="text-align:right;"> 20.8 </td>
## <td style="text-align:right;"> 46.8 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 6 </td>
## <td style="text-align:left;"> Satisfied </td>
## <td style="text-align:right;"> 40 </td>
## <td style="text-align:right;"> 26.0 </td>
## <td style="text-align:right;"> 72.7 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 7 </td>
## <td style="text-align:left;"> Highly satisfied </td>
## <td style="text-align:right;"> 42 </td>
## <td style="text-align:right;"> 27.3 </td>
## <td style="text-align:right;"> 100.0 </td>
## </tr>
## </tbody>
## </table>
Multi-Select
(Check-All-That-Apply) Items
multiselect_summary <- function(data, item_cols, item_labels, group_label) {
# Count how many respondents answered at least one item in this block
group_n <- sum(rowSums(!is.na(data[, item_cols, drop = FALSE])) > 0)
results <- data.frame(
Variable = item_cols,
Item = item_labels,
Times_Selected = sapply(item_cols, function(x) sum(!is.na(data[[x]]))),
stringsAsFactors = FALSE
) %>%
mutate(
`% of All (N)` = round(Times_Selected / nrow(data) * 100, 1),
`% of Group (n)` = round(Times_Selected / group_n * 100, 1)
) %>%
arrange(desc(Times_Selected))
cat("\n**", group_label, "**\n", sep = "")
cat("Group n (answered at least 1):", group_n, "\n\n")
results
}
Where Hear About
Events (Q15)
q15_items <- paste0("Q15_", 1:9)
q15_labels_ms <- c(
"Social media", "Word of mouth", "VisitPittsburgh.com",
"Flyers on the street", "Television", "News",
"Newspaper", "Radio/podcast", "Other"
)
multiselect_summary(df, q15_items, q15_labels_ms, "Where Hear About Events (Q15)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Where Hear About Events (Q15)**
## Group n (answered at least 1): 1429
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q15_1
|
Q15_1
|
Social media
|
1049
|
71.3
|
73.4
|
|
Q15_2
|
Q15_2
|
Word of mouth
|
751
|
51.1
|
52.6
|
|
Q15_5
|
Q15_5
|
Television
|
444
|
30.2
|
31.1
|
|
Q15_6
|
Q15_6
|
News
|
393
|
26.7
|
27.5
|
|
Q15_4
|
Q15_4
|
Flyers on the street
|
390
|
26.5
|
27.3
|
|
Q15_3
|
Q15_3
|
VisitPittsburgh.com
|
367
|
24.9
|
25.7
|
|
Q15_7
|
Q15_7
|
Newspaper
|
290
|
19.7
|
20.3
|
|
Q15_8
|
Q15_8
|
Radio/podcast
|
286
|
19.4
|
20.0
|
|
Q15_9
|
Q15_9
|
Other
|
55
|
3.7
|
3.8
|
Why Stayed in
Pittsburgh (Q26)
q26_items <- paste0("Q26_", 1:10)
q26_labels_ms <- c(
"Professional/Job opportunities", "Connection to professional networks",
"Family/social networks in region", "Educational opportunities",
"School system", "Cost of living",
"Satisfied with quality of life", "Regional amenities",
"Not able to relocate (financial)", "Other"
)
multiselect_summary(df, q26_items, q26_labels_ms, "Why Stayed in Pittsburgh (Q26)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Why Stayed in Pittsburgh (Q26)**
## Group n (answered at least 1): 842
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q26_1
|
Q26_1
|
Professional/Job opportunities
|
534
|
36.3
|
63.4
|
|
Q26_3
|
Q26_3
|
Family/social networks in region
|
499
|
33.9
|
59.3
|
|
Q26_6
|
Q26_6
|
Cost of living
|
467
|
31.7
|
55.5
|
|
Q26_7
|
Q26_7
|
Satisfied with quality of life
|
376
|
25.6
|
44.7
|
|
Q26_4
|
Q26_4
|
Educational opportunities
|
342
|
23.2
|
40.6
|
|
Q26_2
|
Q26_2
|
Connection to professional networks
|
318
|
21.6
|
37.8
|
|
Q26_5
|
Q26_5
|
School system
|
282
|
19.2
|
33.5
|
|
Q26_8
|
Q26_8
|
Regional amenities
|
245
|
16.7
|
29.1
|
|
Q26_9
|
Q26_9
|
Not able to relocate (financial)
|
47
|
3.2
|
5.6
|
|
Q26_10
|
Q26_10
|
Other
|
23
|
1.6
|
2.7
|
What Would Make You
Leave (Q32)
q32_items <- paste0("Q32_", 1:12)
q32_labels_ms <- c(
"Employment opportunities", "Chance of advancing career",
"Better pay elsewhere", "Opportunities for children",
"Seeking better climate (weather)", "Seeking better climate (socio-political)",
"Spouse/partner desires a move", "No desire to leave",
"Experiences of racism/discrimination", "Educational opportunities",
"Professional opportunities", "Other"
)
multiselect_summary(df, q32_items, q32_labels_ms, "What Would Make You Leave (Q32)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **What Would Make You Leave (Q32)**
## Group n (answered at least 1): 839
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q32_1
|
Q32_1
|
Employment opportunities
|
316
|
21.5
|
37.7
|
|
Q32_2
|
Q32_2
|
Chance of advancing career
|
292
|
19.9
|
34.8
|
|
Q32_3
|
Q32_3
|
Better pay elsewhere
|
287
|
19.5
|
34.2
|
|
Q32_8
|
Q32_8
|
No desire to leave
|
261
|
17.7
|
31.1
|
|
Q32_4
|
Q32_4
|
Opportunities for children
|
224
|
15.2
|
26.7
|
|
Q32_7
|
Q32_7
|
Spouse/partner desires a move
|
219
|
14.9
|
26.1
|
|
Q32_5
|
Q32_5
|
Seeking better climate (weather)
|
202
|
13.7
|
24.1
|
|
Q32_9
|
Q32_9
|
Experiences of racism/discrimination
|
186
|
12.6
|
22.2
|
|
Q32_11
|
Q32_11
|
Professional opportunities
|
184
|
12.5
|
21.9
|
|
Q32_10
|
Q32_10
|
Educational opportunities
|
145
|
9.9
|
17.3
|
|
Q32_6
|
Q32_6
|
Seeking better climate (socio-political)
|
134
|
9.1
|
16.0
|
|
Q32_12
|
Q32_12
|
Other
|
24
|
1.6
|
2.9
|
Why Left Pittsburgh
Originally – Returnees (Q38)
q38_items <- paste0("Q38_", 1:23)
q38_labels_ms <- c(
"Viable employment opportunities", "Pursuing education",
"Completed education", "Educational opps for family/social network",
"Diversity in school system", "Diversity in neighborhoods",
"Systemic racism in region", "Larger industry footprint elsewhere",
"Better pay elsewhere", "Region did not feel welcoming",
"Professional networks", "Personal networks",
"Regional amenities", "Family networks in another region",
"Lack of important amenities", "Lack of general amenities (airport, etc.)",
"Lack of culturally relevant amenities", "Family/partner not embedded",
"Neighborhood changes (gentrification)", "Health issues",
"Climate (weather)", "Climate (socio-political)", "Other"
)
multiselect_summary(df, q38_items, q38_labels_ms, "Why Left Pittsburgh Originally – Returnees (Q38)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Why Left Pittsburgh Originally – Returnees (Q38)**
## Group n (answered at least 1): 68
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q38_1
|
Q38_1
|
Viable employment opportunities
|
26
|
1.8
|
38.2
|
|
Q38_2
|
Q38_2
|
Pursuing education
|
25
|
1.7
|
36.8
|
|
Q38_3
|
Q38_3
|
Completed education
|
16
|
1.1
|
23.5
|
|
Q38_9
|
Q38_9
|
Better pay elsewhere
|
16
|
1.1
|
23.5
|
|
Q38_7
|
Q38_7
|
Systemic racism in region
|
13
|
0.9
|
19.1
|
|
Q38_8
|
Q38_8
|
Larger industry footprint elsewhere
|
13
|
0.9
|
19.1
|
|
Q38_13
|
Q38_13
|
Regional amenities
|
12
|
0.8
|
17.6
|
|
Q38_15
|
Q38_15
|
Lack of important amenities
|
12
|
0.8
|
17.6
|
|
Q38_17
|
Q38_17
|
Lack of culturally relevant amenities
|
11
|
0.7
|
16.2
|
|
Q38_21
|
Q38_21
|
Climate (weather)
|
11
|
0.7
|
16.2
|
|
Q38_6
|
Q38_6
|
Diversity in neighborhoods
|
9
|
0.6
|
13.2
|
|
Q38_14
|
Q38_14
|
Family networks in another region
|
9
|
0.6
|
13.2
|
|
Q38_20
|
Q38_20
|
Health issues
|
9
|
0.6
|
13.2
|
|
Q38_10
|
Q38_10
|
Region did not feel welcoming
|
8
|
0.5
|
11.8
|
|
Q38_23
|
Q38_23
|
Other
|
8
|
0.5
|
11.8
|
|
Q38_5
|
Q38_5
|
Diversity in school system
|
7
|
0.5
|
10.3
|
|
Q38_12
|
Q38_12
|
Personal networks
|
6
|
0.4
|
8.8
|
|
Q38_18
|
Q38_18
|
Family/partner not embedded
|
6
|
0.4
|
8.8
|
|
Q38_22
|
Q38_22
|
Climate (socio-political)
|
6
|
0.4
|
8.8
|
|
Q38_4
|
Q38_4
|
Educational opps for family/social network
|
5
|
0.3
|
7.4
|
|
Q38_16
|
Q38_16
|
Lack of general amenities (airport, etc.)
|
5
|
0.3
|
7.4
|
|
Q38_19
|
Q38_19
|
Neighborhood changes (gentrification)
|
5
|
0.3
|
7.4
|
|
Q38_11
|
Q38_11
|
Professional networks
|
4
|
0.3
|
5.9
|
Why Returned to
Pittsburgh (Q40)
q40_items <- paste0("Q40_", 1:20)
q40_labels_ms <- c(
"Viable employment opportunities", "Educational opportunities",
"Diversity in school system", "Diversity in neighborhoods",
"Systemic racism in previous region", "Larger industry footprint in Pittsburgh",
"Better pay in Pittsburgh", "Previous region not welcoming",
"Professional networks in Pittsburgh", "Personal networks in Pittsburgh",
"Regional amenities in Pittsburgh", "Family networks in Pittsburgh",
"Pittsburgh has important amenities", "General amenities (airport, etc.)",
"Culturally relevant amenities", "Family/partner not embedded in previous region",
"Neighborhood changes in previous region", "Climate (weather)",
"Climate (socio-political)", "Other"
)
multiselect_summary(df, q40_items, q40_labels_ms, "Why Returned to Pittsburgh (Q40)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Why Returned to Pittsburgh (Q40)**
## Group n (answered at least 1): 68
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q40_12
|
Q40_12
|
Family networks in Pittsburgh
|
31
|
2.1
|
45.6
|
|
Q40_1
|
Q40_1
|
Viable employment opportunities
|
22
|
1.5
|
32.4
|
|
Q40_2
|
Q40_2
|
Educational opportunities
|
16
|
1.1
|
23.5
|
|
Q40_16
|
Q40_16
|
Family/partner not embedded in previous region
|
13
|
0.9
|
19.1
|
|
Q40_10
|
Q40_10
|
Personal networks in Pittsburgh
|
12
|
0.8
|
17.6
|
|
Q40_9
|
Q40_9
|
Professional networks in Pittsburgh
|
11
|
0.7
|
16.2
|
|
Q40_13
|
Q40_13
|
Pittsburgh has important amenities
|
11
|
0.7
|
16.2
|
|
Q40_18
|
Q40_18
|
Climate (weather)
|
11
|
0.7
|
16.2
|
|
Q40_20
|
Q40_20
|
Other
|
11
|
0.7
|
16.2
|
|
Q40_6
|
Q40_6
|
Larger industry footprint in Pittsburgh
|
10
|
0.7
|
14.7
|
|
Q40_7
|
Q40_7
|
Better pay in Pittsburgh
|
10
|
0.7
|
14.7
|
|
Q40_3
|
Q40_3
|
Diversity in school system
|
9
|
0.6
|
13.2
|
|
Q40_15
|
Q40_15
|
Culturally relevant amenities
|
9
|
0.6
|
13.2
|
|
Q40_5
|
Q40_5
|
Systemic racism in previous region
|
8
|
0.5
|
11.8
|
|
Q40_4
|
Q40_4
|
Diversity in neighborhoods
|
4
|
0.3
|
5.9
|
|
Q40_11
|
Q40_11
|
Regional amenities in Pittsburgh
|
4
|
0.3
|
5.9
|
|
Q40_8
|
Q40_8
|
Previous region not welcoming
|
3
|
0.2
|
4.4
|
|
Q40_14
|
Q40_14
|
General amenities (airport, etc.)
|
3
|
0.2
|
4.4
|
|
Q40_19
|
Q40_19
|
Climate (socio-political)
|
3
|
0.2
|
4.4
|
|
Q40_17
|
Q40_17
|
Neighborhood changes in previous region
|
1
|
0.1
|
1.5
|
What Would Make
Returnees Leave Again (Q44)
q44_items <- paste0("Q44_", 1:9)
q44_labels_ms <- c(
"Employment opportunities", "Chance of advancing career",
"Better pay elsewhere", "Opportunities for children",
"Seeking better climate (weather)", "Seeking better climate (socio-political)",
"Spouse/partner desires a move", "No desire to leave", "Other"
)
multiselect_summary(df, q44_items, q44_labels_ms, "What Would Make Returnees Leave Again (Q44)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **What Would Make Returnees Leave Again (Q44)**
## Group n (answered at least 1): 66
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q44_1
|
Q44_1
|
Employment opportunities
|
42
|
2.9
|
63.6
|
|
Q44_3
|
Q44_3
|
Better pay elsewhere
|
41
|
2.8
|
62.1
|
|
Q44_2
|
Q44_2
|
Chance of advancing career
|
32
|
2.2
|
48.5
|
|
Q44_7
|
Q44_7
|
Spouse/partner desires a move
|
27
|
1.8
|
40.9
|
|
Q44_5
|
Q44_5
|
Seeking better climate (weather)
|
25
|
1.7
|
37.9
|
|
Q44_4
|
Q44_4
|
Opportunities for children
|
23
|
1.6
|
34.8
|
|
Q44_6
|
Q44_6
|
Seeking better climate (socio-political)
|
19
|
1.3
|
28.8
|
|
Q44_8
|
Q44_8
|
No desire to leave
|
8
|
0.5
|
12.1
|
|
Q44_9
|
Q44_9
|
Other
|
3
|
0.2
|
4.5
|
Why Returning to
Pittsburgh – Planned Returnees (Q52)
q52_items <- paste0("Q52_", 1:20)
q52_labels_ms <- c(
"Viable employment opportunities", "Educational opportunities",
"Diversity in school system", "Diversity in neighborhoods",
"Systemic racism in previous region", "Larger industry footprint in Pittsburgh",
"Better pay in Pittsburgh", "Previous region not welcoming",
"Professional networks in Pittsburgh", "Personal networks in Pittsburgh",
"Regional amenities in Pittsburgh", "Family networks in Pittsburgh",
"Pittsburgh has important amenities", "General amenities (airport, etc.)",
"Culturally relevant amenities", "Family/partner not embedded in previous region",
"Neighborhood changes in previous region", "Climate (weather)",
"Climate (socio-political)", "Other"
)
multiselect_summary(df, q52_items, q52_labels_ms, "Why Returning – Planned Returnees (Q52)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Why Returning – Planned Returnees (Q52)**
## Group n (answered at least 1): 71
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q52_12
|
Q52_12
|
Family networks in Pittsburgh
|
24
|
1.6
|
33.8
|
|
Q52_1
|
Q52_1
|
Viable employment opportunities
|
18
|
1.2
|
25.4
|
|
Q52_2
|
Q52_2
|
Educational opportunities
|
17
|
1.2
|
23.9
|
|
Q52_10
|
Q52_10
|
Personal networks in Pittsburgh
|
17
|
1.2
|
23.9
|
|
Q52_8
|
Q52_8
|
Previous region not welcoming
|
16
|
1.1
|
22.5
|
|
Q52_4
|
Q52_4
|
Diversity in neighborhoods
|
15
|
1.0
|
21.1
|
|
Q52_5
|
Q52_5
|
Systemic racism in previous region
|
14
|
1.0
|
19.7
|
|
Q52_7
|
Q52_7
|
Better pay in Pittsburgh
|
13
|
0.9
|
18.3
|
|
Q52_9
|
Q52_9
|
Professional networks in Pittsburgh
|
13
|
0.9
|
18.3
|
|
Q52_13
|
Q52_13
|
Pittsburgh has important amenities
|
13
|
0.9
|
18.3
|
|
Q52_6
|
Q52_6
|
Larger industry footprint in Pittsburgh
|
12
|
0.8
|
16.9
|
|
Q52_15
|
Q52_15
|
Culturally relevant amenities
|
11
|
0.7
|
15.5
|
|
Q52_17
|
Q52_17
|
Neighborhood changes in previous region
|
11
|
0.7
|
15.5
|
|
Q52_3
|
Q52_3
|
Diversity in school system
|
10
|
0.7
|
14.1
|
|
Q52_11
|
Q52_11
|
Regional amenities in Pittsburgh
|
10
|
0.7
|
14.1
|
|
Q52_16
|
Q52_16
|
Family/partner not embedded in previous region
|
10
|
0.7
|
14.1
|
|
Q52_19
|
Q52_19
|
Climate (socio-political)
|
10
|
0.7
|
14.1
|
|
Q52_14
|
Q52_14
|
General amenities (airport, etc.)
|
9
|
0.6
|
12.7
|
|
Q52_18
|
Q52_18
|
Climate (weather)
|
6
|
0.4
|
8.5
|
|
Q52_20
|
Q52_20
|
Other
|
4
|
0.3
|
5.6
|
Why Want to Leave
Pittsburgh (Q56)
q56_items <- paste0("Q56_", 1:23)
q56_labels_ms <- c(
"Viable employment opportunities", "Pursuing education",
"Completed education", "Educational opps for family/social network",
"Diversity in school system", "Diversity in neighborhoods",
"Systemic racism in region", "Larger industry footprint elsewhere",
"Better pay elsewhere", "Region did not feel welcoming",
"Professional networks", "Personal networks",
"Regional amenities", "Family networks in another region",
"Lack of important amenities", "Lack of general amenities (airport, etc.)",
"Lack of culturally relevant amenities", "Family/partner not embedded",
"Neighborhood changes (gentrification)", "Health issues",
"Climate (weather)", "Climate (socio-political)", "Other"
)
multiselect_summary(df, q56_items, q56_labels_ms, "Why Want to Leave Pittsburgh (Q56)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Why Want to Leave Pittsburgh (Q56)**
## Group n (answered at least 1): 207
|
|
Variable
|
Item
|
Times_Selected
|
% of All (N)
|
% of Group (n)
|
|
Q56_7
|
Q56_7
|
Systemic racism in region
|
101
|
6.9
|
48.8
|
|
Q56_1
|
Q56_1
|
Viable employment opportunities
|
99
|
6.7
|
47.8
|
|
Q56_9
|
Q56_9
|
Better pay elsewhere
|
97
|
6.6
|
46.9
|
|
Q56_17
|
Q56_17
|
Lack of culturally relevant amenities
|
65
|
4.4
|
31.4
|
|
Q56_6
|
Q56_6
|
Diversity in neighborhoods
|
62
|
4.2
|
30.0
|
|
Q56_21
|
Q56_21
|
Climate (weather)
|
61
|
4.1
|
29.5
|
|
Q56_5
|
Q56_5
|
Diversity in school system
|
52
|
3.5
|
25.1
|
|
Q56_15
|
Q56_15
|
Lack of important amenities
|
52
|
3.5
|
25.1
|
|
Q56_11
|
Q56_11
|
Professional networks
|
46
|
3.1
|
22.2
|
|
Q56_8
|
Q56_8
|
Larger industry footprint elsewhere
|
45
|
3.1
|
21.7
|
|
Q56_10
|
Q56_10
|
Region did not feel welcoming
|
42
|
2.9
|
20.3
|
|
Q56_14
|
Q56_14
|
Family networks in another region
|
41
|
2.8
|
19.8
|
|
Q56_19
|
Q56_19
|
Neighborhood changes (gentrification)
|
40
|
2.7
|
19.3
|
|
Q56_2
|
Q56_2
|
Pursuing education
|
37
|
2.5
|
17.9
|
|
Q56_4
|
Q56_4
|
Educational opps for family/social network
|
36
|
2.4
|
17.4
|
|
Q56_12
|
Q56_12
|
Personal networks
|
36
|
2.4
|
17.4
|
|
Q56_13
|
Q56_13
|
Regional amenities
|
35
|
2.4
|
16.9
|
|
Q56_22
|
Q56_22
|
Climate (socio-political)
|
35
|
2.4
|
16.9
|
|
Q56_3
|
Q56_3
|
Completed education
|
34
|
2.3
|
16.4
|
|
Q56_20
|
Q56_20
|
Health issues
|
31
|
2.1
|
15.0
|
|
Q56_16
|
Q56_16
|
Lack of general amenities (airport, etc.)
|
26
|
1.8
|
12.6
|
|
Q56_18
|
Q56_18
|
Family/partner not embedded
|
22
|
1.5
|
10.6
|
|
Q56_23
|
Q56_23
|
Other
|
15
|
1.0
|
7.2
|
Industry (Q11)
q11_labels <- c(
"1" = "Advertising and marketing", "2" = "Agriculture/forestry/fishing/hunting/mining",
"3" = "Aerospace", "4" = "Construction", "5" = "Computer and technology",
"6" = "Manufacturing", "7" = "Media and News", "8" = "Retail trade",
"9" = "Transportation/warehousing/utilities", "10" = "Finance/insurance/real estate",
"11" = "Waste management", "12" = "Hospitality", "13" = "Education",
"14" = "Energy", "15" = "Fashion", "16" = "Health care and social assistance",
"17" = "Entertainment", "18" = "Food services",
"19" = "Public administration", "20" = "Nonprofit",
"21" = "Prefer to self-describe"
)
freq_table(df, "Q11", q11_labels, "Industry (Q11)") %>%
kable() %>% kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
##
## **Industry (Q11)** (Q11)
## n = 1286 | Missing = 185 ( 12.6 %)
|
Value
|
Label
|
Count
|
Valid %
|
Cumulative %
|
|
1
|
Advertising and marketing
|
79
|
6.1
|
6.1
|
|
10
|
Finance/insurance/real estate
|
63
|
4.9
|
11.0
|
|
11
|
Waste management
|
16
|
1.2
|
12.3
|
|
12
|
Hospitality
|
38
|
3.0
|
15.2
|
|
13
|
Education
|
136
|
10.6
|
25.8
|
|
14
|
Energy
|
21
|
1.6
|
27.4
|
|
15
|
Fashion
|
29
|
2.3
|
29.7
|
|
16
|
Health care and social assistance
|
112
|
8.7
|
38.4
|
|
17
|
Entertainment
|
21
|
1.6
|
40.0
|
|
18
|
Food services
|
40
|
3.1
|
43.2
|
|
19
|
Public administration
|
24
|
1.9
|
45.0
|
|
2
|
Agriculture/forestry/fishing/hunting/mining
|
54
|
4.2
|
49.2
|
|
20
|
Nonprofit
|
42
|
3.3
|
52.5
|
|
21
|
Prefer to self-describe
|
44
|
3.4
|
55.9
|
|
3
|
Aerospace
|
17
|
1.3
|
57.2
|
|
4
|
Construction
|
134
|
10.4
|
67.7
|
|
5
|
Computer and technology
|
179
|
13.9
|
81.6
|
|
6
|
Manufacturing
|
90
|
7.0
|
88.6
|
|
7
|
Media and News
|
36
|
2.8
|
91.4
|
|
8
|
Retail trade
|
76
|
5.9
|
97.3
|
|
9
|
Transportation/warehousing/utilities
|
35
|
2.7
|
100.0
|
freq_barplot(df, "Q11", q11_labels, "Industry (Q11)", wrap_width = 30)

Dataset Source
Comparison
df %>%
group_by(label) %>%
summarise(
n = n(),
Pct_Female_Q4 = round(mean(Q4 == 2, na.rm = TRUE) * 100, 1),
Pct_Black_Q2 = round(mean(Q2 == 3, na.rm = TRUE) * 100, 1),
Mean_Age_Q6 = round(mean(Q6, na.rm = TRUE), 2),
Mean_Income_Q12 = round(mean(Q12, na.rm = TRUE), 2),
Pct_Born_Pgh_Q22 = round(mean(Q22 == 1, na.rm = TRUE) * 100, 1),
Pct_Currently_In_Pgh = round(mean(Q23 %in% c(1, 3, 4), na.rm = TRUE) * 100, 1),
.groups = "drop"
) %>%
kable(caption = "Key Metrics by Dataset Source",
col.names = c("Dataset", "n", "% Female", "% Black", "Mean Age Group",
"Mean Income Bracket", "% Born in Pittsburgh", "% Currently in Pittsburgh")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)
Key Metrics by Dataset Source
|
Dataset
|
n
|
% Female
|
% Black
|
Mean Age Group
|
Mean Income Bracket
|
% Born in Pittsburgh
|
% Currently in Pittsburgh
|
|
dataset_one
|
213
|
39.0
|
91.5
|
2.99
|
5.93
|
49.8
|
77.5
|
|
dataset_three
|
342
|
37.4
|
90.9
|
2.77
|
6.35
|
55.9
|
76.9
|
|
dataset_two
|
916
|
40.3
|
91.7
|
2.76
|
6.10
|
57.8
|
77.6
|
Session Info
sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: aarch64-apple-darwin20
## Running under: macOS Tahoe 26.2
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: America/New_York
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] janitor_2.2.1 psych_2.5.6 forcats_1.0.0 scales_1.4.0
## [5] kableExtra_1.4.0 knitr_1.50 ggplot2_3.5.2 tidyr_1.3.1
## [9] dplyr_1.1.4 readxl_1.4.5
##
## loaded via a namespace (and not attached):
## [1] generics_0.1.4 xml2_1.4.0 stringi_1.8.7 lattice_0.22-7
## [5] digest_0.6.37 magrittr_2.0.3 evaluate_1.0.5 grid_4.5.1
## [9] timechange_0.3.0 RColorBrewer_1.1-3 fastmap_1.2.0 cellranger_1.1.0
## [13] purrr_1.1.0 viridisLite_0.4.2 textshaping_1.0.1 jquerylib_0.1.4
## [17] mnormt_2.1.1 cli_3.6.5 rlang_1.1.6 withr_3.0.2
## [21] yaml_2.3.10 tools_4.5.1 parallel_4.5.1 vctrs_0.6.5
## [25] R6_2.6.1 lifecycle_1.0.4 lubridate_1.9.4 snakecase_0.11.1
## [29] stringr_1.5.2 pkgconfig_2.0.3 pillar_1.11.0 gtable_0.3.6
## [33] glue_1.8.0 systemfonts_1.2.3 xfun_0.53 tibble_3.3.0
## [37] tidyselect_1.2.1 rstudioapi_0.17.1 farver_2.1.2 htmltools_0.5.8.1
## [41] nlme_3.1-168 rmarkdown_2.29 svglite_2.2.1 labeling_0.4.3
## [45] compiler_4.5.1