Theoretical formulation and statistical framework for content analysis validation
All hypotheses were derived from the integration of Framing Theory and Protection Motivation Theory (PMT) and were tested using chi-square tests of independence and logistic regression models.
Articles dominated by the Disaster frame will be more likely to exhibit higher levels of Perceived Severity than articles dominated by other primary frames.
Articles dominated by the Prevention frame will be more likely to exhibit moderate or strong Response Efficacy than articles dominated by other primary frames.
Articles that include at least one Protective Recommendation will be more likely to exhibit moderate or strong Self-Efficacy than articles without such recommendations.
Articles characterized by an Alarmist emotional tone and the absence of Protective Recommendations will be more likely to combine high Perceived Severity with absent or weak Self-Efficacy than other configurations.
Articles in which Authorities are the dominant source type will be more likely to adopt a Prevention frame than articles in which Citizens/Population are the dominant source type.
There will be systematic differences between newspapers, such that La Voz de Galicia will use the Response frame more frequently than Público, while Público will use the Scientific/Technical frame more frequently than La Voz de Galicia.
Among articles characterized by high perceived threat, those that include at least one Protective Recommendation will be more likely to be classified as High Threat / High Efficacy than as High Threat / Low Efficacy.
All hypotheses were tested using a two-stage analytical strategy. First, bivariate associations were examined using Pearson’s chi-square tests of independence. Second, logistic regression models were estimated to assess the robustness of observed relationships. Effect sizes were reported using Cramér’s V where appropriate. Cases of sparse cell counts, convergence warnings, or perfect separation were explicitly reported and considered in the interpretation of results.
All statistical calculations were performed in Python, and the code used for the analyses is provided below:
import pandas as pd
import numpy as np
from scipy import stats
import statsmodels.api as sm
import statsmodels.formula.api as smf
# =========================
# 1) LOAD DATA
# =========================
df = pd.read_csv("csv/banco-2026-05-21_94837.csv")
# Keep original column names with accents and spaces
COLS = {
"newspaper": "Newspaper",
"primary_frame": "Primary Frame",
"emotional_tone": "Predominant Emotional Tone",
"source_type": "Dominant Source Type",
"severity": "Perceived Severity",
"vulnerability": "Perceived Vulnerability",
"response_efficacy": "Perceived Response Efficacy",
"self_efficacy": "Perceived Self-Efficacy",
"balance": "Threat–Efficacy Balance",
"recommendation": "Protective Recommendation",
}
# =========================
# 2) HELPERS
# =========================
def cramers_v(chi2, table):
n = table.to_numpy().sum()
r, c = table.shape
return np.sqrt(chi2 / (n * (min(r - 1, c - 1))))
def chi_square_test(data, var1, var2):
table = pd.crosstab(data[var1], data[var2])
chi2, p, dof, expected = stats.chi2_contingency(table)
v = cramers_v(chi2, table)
return table, chi2, p, dof, v
def logistic_binomial(data, y_col, x_col):
"""
Fits a simple logistic regression: y ~ x
"""
formula = f"{y_col} ~ {x_col}"
model = smf.logit(formula, data=data).fit(disp=0)
params = model.params
conf = model.conf_int()
or_vals = np.exp(params)
or_ci_low = np.exp(conf[0])
or_ci_high = np.exp(conf[1])
pvals = model.pvalues
out = pd.DataFrame({
"term": params.index,
"coef": params.values,
"OR": or_vals.values,
"CI_low": or_ci_low.values,
"CI_high": or_ci_high.values,
"p": pvals.values
})
return model, out
# =========================
# 3) RECODINGS FOR HYPOTHESES
# =========================
# H1: Disaster frame -> strong perceived severity
df["H1_x"] = (df[COLS["primary_frame"]] == "Disaster").astype(int)
df["H1_y"] = (df[COLS["severity"]] == "Strong").astype(int)
# H2: Prevention frame -> moderate/strong response efficacy
df["H2_x"] = (df[COLS["primary_frame"]] == "Prevention").astype(int)
df["H2_y"] = df[COLS["response_efficacy"]].isin(["Moderate", "Strong"]).astype(int)
# H3: Protective recommendation -> moderate/strong self-efficacy
df["H3_x"] = (df[COLS["recommendation"]] == "Sim").astype(int)
df["H3_y"] = df[COLS["self_efficacy"]].isin(["Moderate", "Strong"]).astype(int)
# H4: Alarmist tone + no recommendation -> High Threat / Low Efficacy
df["H4_x"] = (
(df[COLS["emotional_tone"]] == "Alarmist") &
(df[COLS["recommendation"]] == "Não")
).astype(int)
df["H4_y"] = (df[COLS["balance"]] == "High Threat / Low Efficacy").astype(int)
# H5: Authorities vs Citizens/Population -> Prevention frame
df_h5 = df[df[COLS["source_type"]].isin(["Authorities", "Population/Citizens"])].copy()
df_h5["H5_x"] = (df_h5[COLS["source_type"]] == "Authorities").astype(int)
df_h5["H5_y"] = (df_h5[COLS["primary_frame"]] == "Prevention").astype(int)
# H6: Newspaper differences
# Part A: Response frame
df["H6a_x"] = (df[COLS["newspaper"]] == "La Voz de Galicia").astype(int)
df["H6a_y"] = (df[COLS["primary_frame"]] == "Response").astype(int)
# Part B: Scientific/Technical frame
df["H6b_x"] = (df[COLS["newspaper"]] == "Público").astype(int)
df["H6b_y"] = (df[COLS["primary_frame"]] == "Scientific/Technical").astype(int)
# H7: among high-threat articles, recommendation -> High Threat / High Efficacy
df_h7 = df[df[COLS["balance"]].str.contains("High Threat", na=False)].copy()
df_h7["H7_x"] = (df_h7[COLS["recommendation"]] == "Sim").astype(int)
df_h7["H7_y"] = (df_h7[COLS["balance"]] == "High Threat / High Efficacy").astype(int)
# =========================
# 4) RUN TESTS
# =========================
results = []
# H1
table, chi2, p, dof, v = chi_square_test(df, "H1_x", "H1_y")
model, logit_df = logistic_binomial(df, "H1_y", "H1_x")
results.append({
"Hypothesis": "H1", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H1", "Test": "Logistic", "N": len(df), "OR": np.exp(model.params["H1_x"]),
"CI_low": np.exp(model.conf_int().loc["H1_x"])[0], "CI_high": np.exp(model.conf_int().loc["H1_x"])[1], "p": model.pvalues["H1_x"]
})
# H2
table, chi2, p, dof, v = chi_square_test(df, "H2_x", "H2_y")
model, logit_df = logistic_binomial(df, "H2_y", "H2_x")
results.append({
"Hypothesis": "H2", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H2", "Test": "Logistic", "N": len(df), "OR": np.exp(model.params["H2_x"]),
"CI_low": np.exp(model.conf_int().loc["H2_x"])[0], "CI_high": np.exp(model.conf_int().loc["H2_x"])[1], "p": model.pvalues["H2_x"]
})
# H3
table, chi2, p, dof, v = chi_square_test(df, "H3_x", "H3_y")
model, logit_df = logistic_binomial(df, "H3_y", "H3_x")
results.append({
"Hypothesis": "H3", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H3", "Test": "Logistic", "N": len(df), "OR": np.exp(model.params["H3_x"]),
"CI_low": np.exp(model.conf_int().loc["H3_x"])[0], "CI_high": np.exp(model.conf_int().loc["H3_x"])[1], "p": model.pvalues["H3_x"]
})
# H4
table, chi2, p, dof, v = chi_square_test(df, "H4_x", "H4_y")
model, logit_df = logistic_binomial(df, "H4_y", "H4_x")
results.append({
"Hypothesis": "H4", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H4", "Test": "Logistic", "N": len(df), "OR": np.exp(model.params["H4_x"]),
"CI_low": np.exp(model.conf_int().loc["H4_x"])[0], "CI_high": np.exp(model.conf_int().loc["H4_x"])[1], "p": model.pvalues["H4_x"]
})
# H5
table, chi2, p, dof, v = chi_square_test(df_h5, "H5_x", "H5_y")
model, logit_df = logistic_binomial(df_h5, "H5_y", "H5_x")
results.append({
"Hypothesis": "H5", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H5", "Test": "Logistic", "N": len(df_h5), "OR": np.exp(model.params["H5_x"]),
"CI_low": np.exp(model.conf_int().loc["H5_x"])[0], "CI_high": np.exp(model.conf_int().loc["H5_x"])[1], "p": model.pvalues["H5_x"]
})
# H6a Response frame
table, chi2, p, dof, v = chi_square_test(df, "H6a_x", "H6a_y")
model, logit_df = logistic_binomial(df, "H6a_y", "H6a_x")
results.append({
"Hypothesis": "H6a", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H6a", "Test": "Logistic", "N": len(df), "OR": np.exp(model.params["H6a_x"]),
"CI_low": np.exp(model.conf_int().loc["H6a_x"])[0], "CI_high": np.exp(model.conf_int().loc["H6a_x"])[1], "p": model.pvalues["H6a_x"]
})
# H6b Scientific/Technical frame
table, chi2, p, dof, v = chi_square_test(df, "H6b_x", "H6b_y")
model, logit_df = logistic_binomial(df, "H6b_y", "H6b_x")
results.append({
"Hypothesis": "H6b", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H6b", "Test": "Logistic", "N": len(df), "OR": np.exp(model.params["H6b_x"]),
"CI_low": np.exp(model.conf_int().loc["H6b_x"])[0], "CI_high": np.exp(model.conf_int().loc["H6b_x"])[1], "p": model.pvalues["H6b_x"]
})
# H7
table, chi2, p, dof, v = chi_square_test(df_h7, "H7_x", "H7_y")
model, logit_df = logistic_binomial(df_h7, "H7_y", "H7_x")
results.append({
"Hypothesis": "H7", "Test": "Chi-square", "N": int(table.to_numpy().sum()),
"Chi2": chi2, "df": dof, "p": p, "CramersV": v, "Decision": "supported" if p < 0.05 else "not supported"
})
results.append({
"Hypothesis": "H7", "Test": "Logistic", "N": len(df_h7), "OR": np.exp(model.params["H7_x"]),
"CI_low": np.exp(model.conf_int().loc["H7_x"])[0], "CI_high": np.exp(model.conf_int().loc["H7_x"])[1], "p": model.pvalues["H7_x"]
})
# =========================
# 5) SAVE OUTPUTS
# =========================
results_df = pd.DataFrame(results)
results_df.to_csv("H1_H7_results.csv", index=False)
print(results_df)