This uses data prior to the first exam only.
library(tidyverse)
library(car)
library(nlme)
library(haven)
library(tibble)
recode_df <- read_csv('/Users/joshuarosenberg/Dropbox/1_Research/Engagement/yday.csv')
recode <- filter(recode_df, yday <= 48)
recode <- mutate(recode, wave = case_when(
yday <= 4 ~ 12,
yday > 4 & yday <= 8 ~ 11,
yday > 8 & yday <= 12 ~ 10,
yday > 12 & yday <= 16 ~ 9,
yday > 16 & yday <= 20 ~ 8,
yday > 20 & yday <= 24 ~ 7,
yday > 24 & yday <= 28 ~ 6,
yday > 28 & yday <= 32 ~ 5,
yday > 32 & yday <= 36 ~ 4,
yday > 36 & yday <= 40 ~ 3,
yday > 40 & yday <= 44 ~ 2,
yday > 44 & yday <= 48 ~ 1
))
recode <- recode %>%
group_by(student_ID, wave) %>%
summarize(mean_time_watched_minutes = sum(time_watched_minutes, na.rm = T))
recode_df %>%
group_by(yday) %>%
summarize(sum_tw = sum(time_watched_minutes)) %>%
ggplot(aes(x = yday, y = sum_tw)) +
geom_point() +
geom_line()
# library(lme4)
#
# m1 <- lmer(time_watched_minutes ~ wave + I(wave ^ 2) +
# (wave + I(wave ^ 2) | student_ID), data = recode,
# control = lmerControl(optimizer="bobyqa", optCtrl = list(maxfun = 100000)))
recode.grouped <- groupedData(mean_time_watched_minutes ~ wave|student_ID, data = recode, order.groups = F)
ctrl <- lmeControl(opt='optim', maxIter=1e8, msMaxIter = 1e8)
model5.1a <- lme(mean_time_watched_minutes ~ wave,
random = ~ wave,, method = "REML",
data = recode.grouped, na.action = na.omit, control = ctrl)
# model5.1b <- lme(time_watched_minutes ~ wave + I(wave^2) + I(wave ^ 3),
# random = ~ wave + I(wave^2) + I(wave ^ 3), method = "REML",
# data = recode.grouped, na.action = na.omit, control = ctrl)
#
# model5.1bb <- lme(time_watched_minutes ~ wave + I(wave^2) + I(wave ^ 3),
# random = ~ wave + I(wave^2) + I(wave ^ 3), method = "REML",
# data = recode.grouped, na.action = na.omit, control = ctrl)
# pick a model
model5.1 <- model5.1a
# model5.1 <- lme(View~TimeN_tilexam + I(TimeN_tilexam^2) + I(TimeN_tilexam^3)
# + I(TimeN_tilexam^4) + I(TimeN_tilexam^5),
# random = ~TimeN_tilexam , method = "REML",
# data = recode.grouped, na.action=na.omit)
# summary(model5.1)
# model1a <- lme(View ~ TimeN_tilexam + I(TimeN_tilexam^2),
# random = ~TimeN_tilexam + I(TimeN_tilexam^2), method = "REML",
# data = recode.grouped, na.action=na.omit, control = ctrl)
# summary(model1a)
#
# model5.1 <- model1b
broom::glance(model5.1)
## sigma logLik AIC BIC deviance
## 1 31.09569 -15968.07 31948.14 31984.68 NA
dff <- rownames_to_column(random.effects(model5.1))
dff <- tbl_df(dff)
names(dff) <- c("ID", "intercept", "linear_slope")
dff <- mutate(dff,
intercept = intercept + fixed.effects(model5.1)[1],
linear_slope = linear_slope + fixed.effects(model5.1)[2])
# names(dff) <- c("ID", "intercept", "linear_slope", "quadratic_slope", "cubic_slope")
#
# dff <- mutate(dff,
# intercept = intercept + 17.52,
# linear_slope = linear_slope - 6.61,
# quadratic_slope = quadratic_slope + .979,
# cubic_slope = cubic_slope - .045)
dff$ID <- as.integer(dff$ID)
df_coef <- tbl_df(coef(model5.1))
names(df_coef) <- c("unadjusted_intercept", "unadjusted_linear_slope")
# names(df_coef) <- c("unadjusted_intercept", "unadjusted_linear_slope", "quadratic_slope", "cubic_slope")
df_coef <- df_coef %>% rownames_to_column() %>% rename(ID = rowname) %>% mutate(ID = as.integer(ID))
dff <- left_join(dff, df_coef)
grades_data <- read_sav("~/Dropbox/1_research/Engagement/data/all_three_semesters.sav")
grades_data <- select(grades_data, ID, contains("Exam"), FinalGrade)
grades_data$ID <- as.integer(grades_data$ID)
IDs_data <- read_csv("~/Dropbox/1_Research/Engagement/Archive/ss15_key.csv")
IDs_data <- select(IDs_data, ID, username = Username)
demographic_data <- left_join(IDs_data, grades_data, by = "ID")
df <- left_join(dff, demographic_data)
all_three_semesters <- read_sav("~/Dropbox/1_Research/Engagement/Data/all_three_semesters.sav")
all_three_semesters$ID <- as.integer(all_three_semesters$ID)
dfm <- left_join(df, all_three_semesters, by = "ID")
dfm$T1Q003 <- ifelse(dfm$T1Q003 == 1, 5,
ifelse(dfm$T1Q003 == 2, 4,
ifelse(dfm$T1Q003 == 4, 2,
ifelse(dfm$T1Q003 == 5, 1, NA))))
dfm$T1Q017 <- ifelse(dfm$T1Q017 == 1, 5,
ifelse(dfm$T1Q017 == 2, 4,
ifelse(dfm$T1Q017 == 4, 2,
ifelse(dfm$T1Q017 == 5, 1, NA))))
dfm$T1Q042 <- ifelse(dfm$T1Q042 == 1, 5,
ifelse(dfm$T1Q042 == 2, 4,
ifelse(dfm$T1Q042 == 4, 2,
ifelse(dfm$T1Q042 == 5, 1, NA))))
dfm$T1Q034 <- ifelse(dfm$T1Q034 == 1, 5,
ifelse(dfm$T1Q034 == 2, 4,
ifelse(dfm$T1Q034 == 4, 2,
ifelse(dfm$T1Q034 == 5, 1, NA))))
dfm$T1Q025 <- ifelse(dfm$T1Q025 == 1, 5,
ifelse(dfm$T1Q025 == 2, 4,
ifelse(dfm$T1Q025 == 4, 2,
ifelse(dfm$T1Q025 == 5, 1, NA))))
dfm$cost_value <- jmRtools::composite_mean_maker(dfm, T1Q003, T1Q017, T1Q042, T1Q034, T1Q025)
dfm$perceived_competence <- jmRtools::composite_mean_maker(dfm, T1Q016, T1Q007, T1Q028, T1Q035, T1Q022)
dfm$utility_value <- jmRtools::composite_mean_maker(dfm, T1Q038, T1Q014, T1Q026, T1Q005, T1Q043)
dfm$interest_value <- jmRtools::composite_mean_maker(dfm, T1Q036, T1Q019, T1Q001, T1Q032, T1Q041)
dfm$attainment_value <- jmRtools::composite_mean_maker(dfm, T1Q024, T1Q009, T1Q045, T1Q030, T1Q012)
dfm$task_value <- jmRtools::composite_mean_maker(dfm, T1Q038, T1Q014, T1Q026, T1Q005, T1Q043, T1Q036, T1Q019, T1Q001, T1Q032, T1Q041, T1Q024, T1Q009, T1Q045, T1Q030, T1Q012)
dfm$mastery_approach <- jmRtools::composite_mean_maker(dfm, T1Q003, T1Q012, T1Q020, T1Q024, T1Q027)
dfm$mastery_avoid <- jmRtools::composite_mean_maker(dfm, T1Q036, T1Q015, T1Q006, T1Q022)
dfm$performance_approach <- jmRtools::composite_mean_maker(dfm, T1Q034, T1Q008, T1Q033, T1Q025, T1Q039)
dfm$performance_avoid <- jmRtools::composite_mean_maker(dfm, T1Q041, T1Q030, T1Q018, T1Q004)
dfm <- dfm %>% select(ID, cost_value, perceived_competence, utility_value, final_grade = Percent_FinalGrade, intercept, linear_slope, task_value, mastery_approach, mastery_avoid, performance_approach, performance_avoid)
# dfm <- dfm %>% select(ID, cost_value, perceived_competence, utility_value, final_grade = Percent_FinalGrade, intercept, linear_slope, quadratic_slope, cubic_slope, task_value)
library(prcr)
x <- create_profiles(dfm, mastery_approach, performance_approach, performance_avoid, n_profiles=3, to_center=T, to_scale=T)
plot(x)
For some reason, we get a different overall model when we use the population-level (not ID, i.e. not student but overall) predictions. There’s also some code for using the equation.
Here’s one using the student-level predictions.
In these models, the intercepts and slopes are adjusted based on the random effects for those terms.
Look out, lots going on.
dfm_b <- dfm
predictions <- predict(model5.1, recode.grouped, level = 1)
df_for_plot <- as.tibble(bind_cols(as.data.frame(recode.grouped), predictions = predictions))
df_for_plot$student_ID <- as.integer(as.character(df_for_plot$student_ID))
# x <- 0:5
# dat <- data.frame(x,
# y = 8.15 - (15.12 * x) + (14.78 * (x ^ 2)) - (6.12 * (x ^ 3)) + (1.15 * (x ^ 4)) - (.08 * (x ^ 5)))
#
# f <- function(x) 8.15 - (15.12 * x) + (14.78 * (x ^ 2)) - (6.12 * (x ^ 3)) + (1.15 * (x ^ 4)) - (.08 * (x ^ 5))
# ggplot(dat, aes(x,y)) +
# stat_function(fun=f, colour="black") +
# hrbrthemes::theme_ipsum() +
# ylab("Mean Minutes Watched / Day") +
# xlab("Weeks Before Exam")
dfm <- rename(df, student_ID = ID)
dfm$final_grade <- as.vector(dfm$final_grade)
## Warning: Unknown or uninitialised column: 'final_grade'.
df_for_plot %>%
left_join(dfm) %>%
filter(!is.na(FinalGrade) & !is.na(student_ID)) %>%
select(student_ID, wave, predictions) %>%
group_by(student_ID, wave) %>%
summarize(predictions = sum(predictions, na.rm = T)) %>%
spread(wave, predictions) %>%
gather(key, val, -student_ID) %>%
ungroup() %>%
mutate(ID = as.factor(student_ID),
key = factor(key, levels = 0:12)) %>%
ggplot(aes(x = key, y = val, group = ID)) +
geom_point() +
geom_line() +
viridis::scale_color_viridis() +
hrbrthemes::theme_ipsum() +
xlab("Wave") +
ylab("Mean Minutes Viewed")
## Joining, by = "student_ID"
Here are some models, first for antecedents of different intercepts and slopes.
##
## Call:
## lm(formula = intercept ~ task_value * perceived_competence, data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -23.736 -11.718 0.088 8.282 74.968
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 7.743 42.850 0.181 0.857
## task_value 7.768 10.659 0.729 0.467
## perceived_competence 4.103 10.748 0.382 0.703
## task_value:perceived_competence -1.334 2.579 -0.517 0.605
##
## Residual standard error: 15.12 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.005945, Adjusted R-squared: -0.00608
## F-statistic: 0.4944 on 3 and 248 DF, p-value: 0.6865
##
## Call:
## lm(formula = linear_slope ~ task_value * perceived_competence,
## data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -6.7341 -0.7320 0.0151 1.0462 2.1072
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.1867 3.8375 0.049 0.961
## task_value -0.6895 0.9546 -0.722 0.471
## perceived_competence -0.3619 0.9626 -0.376 0.707
## task_value:perceived_competence 0.1180 0.2309 0.511 0.610
##
## Residual standard error: 1.354 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.0059, Adjusted R-squared: -0.006125
## F-statistic: 0.4906 on 3 and 248 DF, p-value: 0.6891
##
## Call:
## lm(formula = intercept ~ cost_value * perceived_competence, data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -25.093 -11.067 -0.411 8.480 73.828
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -24.114 29.357 -0.821 0.4122
## cost_value 17.012 8.479 2.006 0.0459 *
## perceived_competence 13.534 7.005 1.932 0.0545 .
## cost_value:perceived_competence -3.956 1.978 -2.000 0.0466 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 15.04 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.01606, Adjusted R-squared: 0.004161
## F-statistic: 1.35 on 3 and 248 DF, p-value: 0.2589
## To invalidate the inference, 1.84 % of the estimate would have to be due to bias.
## To invalidate the inference, 5 observations would have to be replaced with cases for which there is no effect.
##
## Call:
## lm(formula = linear_slope ~ cost_value * perceived_competence,
## data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -6.6274 -0.7628 0.0645 0.9673 2.2309
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.9920 2.6296 1.138 0.2563
## cost_value -1.4998 0.7595 -1.975 0.0494 *
## perceived_competence -1.1998 0.6275 -1.912 0.0570 .
## cost_value:perceived_competence 0.3499 0.1771 1.975 0.0493 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.347 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.01561, Adjusted R-squared: 0.003707
## F-statistic: 1.311 on 3 and 248 DF, p-value: 0.2713
##
## Call:
## lm(formula = intercept ~ utility_value * perceived_competence,
## data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -23.686 -11.581 0.325 8.557 74.870
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 31.208 41.564 0.751 0.453
## utility_value 1.720 9.833 0.175 0.861
## perceived_competence -1.864 10.736 -0.174 0.862
## utility_value:perceived_competence 0.153 2.458 0.062 0.950
##
## Residual standard error: 15.12 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.005249, Adjusted R-squared: -0.006784
## F-statistic: 0.4362 on 3 and 248 DF, p-value: 0.7273
##
## Call:
## lm(formula = linear_slope ~ utility_value * perceived_competence,
## data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -6.7254 -0.7508 -0.0048 1.0095 2.1020
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.90310 3.72251 -0.511 0.610
## utility_value -0.14975 0.88063 -0.170 0.865
## perceived_competence 0.16737 0.96149 0.174 0.862
## utility_value:perceived_competence -0.01414 0.22016 -0.064 0.949
##
## Residual standard error: 1.354 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.00512, Adjusted R-squared: -0.006915
## F-statistic: 0.4254 on 3 and 248 DF, p-value: 0.7349
Here are models with the antecedents predicting final grade
m3 <- lm(final_grade ~ task_value * perceived_competence, data = dfm_b)
summary(m3)
##
## Call:
## lm(formula = final_grade ~ task_value * perceived_competence,
## data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -41.019 -7.050 1.993 7.841 20.421
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 11.364 29.427 0.386 0.6997
## task_value 14.918 7.320 2.038 0.0426 *
## perceived_competence 14.440 7.381 1.956 0.0515 .
## task_value:perceived_competence -2.836 1.771 -1.601 0.1106
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.38 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.09329, Adjusted R-squared: 0.08232
## F-statistic: 8.506 on 3 and 248 DF, p-value: 2.125e-05
konfound::konfound(m3, task_value)
## To invalidate the inference, 3.36 % of the estimate would have to be due to bias.
## To invalidate the inference, 8 observations would have to be replaced with cases for which there is no effect.
m4 <- lm(final_grade ~ mastery_approach + performance_approach + performance_avoid, data = dfm_b)
summary(m4)
##
## Call:
## lm(formula = final_grade ~ mastery_approach + performance_approach +
## performance_avoid, data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -40.111 -7.308 1.791 8.539 20.870
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 68.0625 5.6055 12.142 <2e-16 ***
## mastery_approach 0.5390 1.8857 0.286 0.7752
## performance_approach 0.2353 1.3403 0.176 0.8608
## performance_avoid 3.4975 1.7647 1.982 0.0486 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.67 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.04301, Adjusted R-squared: 0.03143
## F-statistic: 3.715 on 3 and 248 DF, p-value: 0.01214
konfound::konfound(m4, performance_avoid)
## To invalidate the inference, 0.63 % of the estimate would have to be due to bias.
## To invalidate the inference, 2 observations would have to be replaced with cases for which there is no effect.
m3 <- lm(final_grade ~ cost_value * perceived_competence, data = dfm_b)
summary(m3)
##
## Call:
## lm(formula = final_grade ~ cost_value * perceived_competence,
## data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -41.581 -6.738 1.752 7.463 20.691
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 36.204 20.359 1.778 0.0766 .
## cost_value 8.766 5.881 1.491 0.1373
## perceived_competence 9.632 4.858 1.983 0.0485 *
## cost_value:perceived_competence -1.614 1.371 -1.177 0.2404
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.43 on 248 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.08473, Adjusted R-squared: 0.07366
## F-statistic: 7.653 on 3 and 248 DF, p-value: 6.522e-05
Also looked at outcomes. Adjusted the linear term to be associated with changes per day, though I’m still having a hard time interpreting these coefficients. That said, it doesn’t look like the intercept is (linearly) associated with lower final grades, which is somewhat surprising, but maybe not entirely because quartile 3 demonstrated higher final grades.
##
## Call:
## lm(formula = final_grade ~ intercept + I(linear_slope * 4) +
## task_value + perceived_competence, data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -33.175 -5.023 0.790 6.442 21.467
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -84.415 20.168 -4.186 3.96e-05 ***
## intercept 13.703 1.937 7.074 1.54e-11 ***
## I(linear_slope * 4) 37.701 5.408 6.972 2.84e-11 ***
## task_value 2.907 1.306 2.225 0.0270 *
## perceived_competence 3.169 1.341 2.363 0.0189 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 9.146 on 247 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.2991, Adjusted R-squared: 0.2878
## F-statistic: 26.36 on 4 and 247 DF, p-value: < 2.2e-16
##
## Call:
## lm(formula = final_grade ~ intercept, data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -36.255 -6.785 1.107 8.263 20.523
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 76.98386 1.57793 48.788 < 2e-16 ***
## intercept 0.20928 0.04304 4.862 2.04e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.35 on 254 degrees of freedom
## (16 observations deleted due to missingness)
## Multiple R-squared: 0.08514, Adjusted R-squared: 0.08154
## F-statistic: 23.64 on 1 and 254 DF, p-value: 2.039e-06
##
## Call:
## lm(formula = final_grade ~ linear_slope, data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -36.418 -6.895 1.096 8.242 20.612
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 79.2213 1.1978 66.140 < 2e-16 ***
## linear_slope -2.2771 0.4818 -4.726 3.79e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.37 on 254 degrees of freedom
## (16 observations deleted due to missingness)
## Multiple R-squared: 0.08083, Adjusted R-squared: 0.07721
## F-statistic: 22.34 on 1 and 254 DF, p-value: 3.794e-06
##
## Call:
## lm(formula = final_grade ~ intercept + I(linear_slope * 4) +
## cost_value + perceived_competence, data = dfm_b)
##
## Residuals:
## Min 1Q Median 3Q Max
## -33.714 -5.088 0.620 6.588 20.873
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -78.8755 20.2782 -3.890 0.000129 ***
## intercept 13.3776 1.9701 6.790 8.28e-11 ***
## I(linear_slope * 4) 36.7772 5.4995 6.687 1.51e-10 ***
## cost_value 1.0760 0.8437 1.275 0.203384
## perceived_competence 4.4864 1.1391 3.938 0.000107 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 9.207 on 247 degrees of freedom
## (20 observations deleted due to missingness)
## Multiple R-squared: 0.2898, Adjusted R-squared: 0.2783
## F-statistic: 25.19 on 4 and 247 DF, p-value: < 2.2e-16
dfm_b %>% ggplot(aes(y = final_grade, x = intercept)) + geom_point() + geom_smooth(method = "lm")
## Warning: Removed 16 rows containing non-finite values (stat_smooth).
## Warning: Removed 16 rows containing missing values (geom_point).
dfm_b %>% ggplot(aes(y = final_grade, x = linear_slope)) + geom_point() + geom_smooth(method = 'lm')
## Warning: Removed 16 rows containing non-finite values (stat_smooth).
## Warning: Removed 16 rows containing missing values (geom_point).
dfm_b %>% ggplot(aes(y = final_grade, x = intercept)) + geom_point() + geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
## Warning: Removed 16 rows containing non-finite values (stat_smooth).
## Warning: Removed 16 rows containing missing values (geom_point).
dfm_b %>% ggplot(aes(y = final_grade, x = linear_slope)) + geom_point() + geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
## Warning: Removed 16 rows containing non-finite values (stat_smooth).
## Warning: Removed 16 rows containing missing values (geom_point).