To run a logistic regression in R, you fit a generalized linear model with glm(outcome ~ predictors, data = df, family = binomial), read the coefficients with summary(), and then convert them from log-odds into odds ratios with exp(), because the raw output is on a scale almost nobody reports. The defensible model depends on a binary outcome coded correctly, checking fit and classification, and interpreting each odds ratio against the reference. This guide covers the full workflow with code.

When logistic regression is the right model

Use logistic regression when the outcome is binary, such as pass or fail, relapse or not, survived or died. A continuous outcome calls for linear regression in R instead. The model predicts the probability of the event and expresses each predictor's influence as an effect on the odds. The conceptual background is in interpreting logistic regression.

Coding the outcome and fitting the model

R models the probability of the second level of a factor, so make the outcome a factor and confirm the reference is what you intend:

df$passed <- factor(df$passed, levels = c("no", "yes"))
model <- glm(passed ~ hours + prior_gpa, data = df, family = binomial)
summary(model)

Here R models the odds of yes because it is the second level. Getting the reference wrong flips every interpretation, which is the most common silent error in a logistic analysis.

From log-odds to odds ratios

The coefficients in summary() are on the log-odds scale and are not directly interpretable. Exponentiate them to get odds ratios and their confidence intervals:

exp(coef(model))            # odds ratios
exp(confint(model))         # 95% confidence intervals

An odds ratio above 1 means the predictor raises the odds of the event; below 1 means it lowers them. For each extra study hour an odds ratio of 1.45 means the odds of passing are 45% higher, holding the other predictors constant.

Assessing model fit and significance

Logistic regression has no R-squared, so fit is judged differently. Test the whole model against the null with a likelihood-ratio test, and look at a pseudo R-squared for a rough sense of explained variation:

# overall model test (chi-square on the deviance)
anova(model, test = "Chisq")

# McFadden pseudo R-squared
library(pscl)
pR2(model)

The Hosmer-Lemeshow test and a classification table add a check on calibration and accuracy. Significance of an individual predictor is read from the z value and its p in the summary, the same logic explained in understanding p-values.

Predicted probabilities and classification

To turn the model into predictions, request probabilities on the response scale and threshold them, or evaluate ranking with the area under the ROC curve:

df$prob <- predict(model, type = "response")

library(pROC)
roc(df$passed, df$prob)   # AUC: discrimination

Report the AUC rather than a single accuracy figure when the classes are imbalanced, because accuracy alone can look high simply by predicting the majority class every time.

Watching for separation and small cells

If R prints fitted probabilities numerically 0 or 1 occurred, you have complete or quasi-complete separation: a predictor perfectly splits the outcome and the coefficients blow up. The fixes are to merge sparse categories, drop the offending predictor, or use a penalized method such as Firth's logistic regression from the logistf package. Sparse cells also follow from too few events per predictor, the same sample-size logic covered in power analysis and sample size.

Reporting logistic regression in APA style

Report each predictor as its odds ratio with a 95% confidence interval and p-value, plus the overall model test. A clean sentence reads: study hours significantly predicted passing, OR = 1.45, 95% CI [1.18, 1.79], p < .001; the model significantly improved on the null, chi-square(2) = 31.4, p < .001. The formatting rules are in APA-style templates for each test, and the narrative belongs in your results section. When the outcome is a time-to-event rather than a yes or no, you need survival analysis in R.