Participants
# Age
age <- as.numeric(dat$age)
age_m <- round(mean(age, na.rm = TRUE), 2)
age_sd <- round(sd(age, na.rm = TRUE), 2)
# Gender
gender_counts <- table(dat$gender)
gender_pct <- round(100 * prop.table(gender_counts), 2)
# Education
edu_levels <- c("Less than high school", "High school diploma or GED",
"Some college", "2 year degree", "4 year degree",
"Master's degree", "Doctoral or professional degree",
"Prefer not to say")
edu <- factor(dat$education, levels = edu_levels)
# Income
inc_levels <- c("Under $25,000", "$25,000-$49,999", "$50,000-$74,999",
"$75,000-$99,999", "$100,000-$149,999", "$150,000+",
"Prefer not to say")
inc <- factor(dat$income, levels = inc_levels)
# Politics
pol_levels <- c("Very liberal", "Liberal", "Slightly liberal",
"Neither liberal nor conservative (Moderate)",
"Slightly conservative", "Conservative", "Very conservative")
pol <- factor(dat$politics, levels = pol_levels)
# Rurality
rur_levels <- c("Yes, I grew up on a farm",
"Yes, I grew up in a rural area where farming was common",
"No, but I have close family or friends who farm",
"No connections to farming")
rur <- factor(dat$rural, levels = rur_levels)
# Ag importance
ag_levels <- c("Not at all important", "Slightly important",
"Moderately important", "Very important", "Extremely important")
ag <- factor(dat$ag.importance, levels = ag_levels)
Participants were n = 65 adults recruited via Prolific. 6 of
these participants were removed for failing the attention /
comprehension check. The following statistics reflect the remaining
59.
Mean age was 37.9 years (SD = 14.56). 37 participants
identified as female (62.71%), 22 as male (37.29%), and 0 identified as
non-binary or preferred not to say.
library(stringr)
par(mfrow = c(5, 1), mar = c(4, 15, 3, 2))
plot_demo <- function(x, title, wrap_width = 25) {
x <- x[!is.na(x)]
tab <- table(x)
names(tab) <- str_wrap(names(tab), width = wrap_width)
barplot(tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = title,
cex.main = 1.6,
col = "grey75",
border = "black",
xlim = c(0, max(tab) * 1.1))
}
plot_demo(edu, "Education Level")
plot_demo(inc, "Income")
plot_demo(pol, "Political Views")
plot_demo(rur, "Farm Background")
plot_demo(ag, "Importance of Agriculture in Community")
It’s worth noting this sample is small, skews liberal, and slightly
overrepresents higher education levels.
Results
Initial Impressions
Before any standardized questions, participants were asked to share
their first thoughts about agrivoltaics after a very brief explanation
of what agrivoltaics are. Specifically, they were asked about installing
solar panels on farmland. Responses were coded into three
categories:
- Unqualified positive: enthusiastic with no hedging
- “I love this idea, smart land use is essential to sustainable
farming practices”
- “The system yields double positive outcomes. There is full
optimization of resources.”
- “Never heard of it, but it sounds like a great idea actually!”
- Positive with hedges: favorable but conditional, or
reflecting awareness that more information is needed
- “I think it’s a good idea in theory if it actually works. My
question is, who monitors output? Who is responsible for maintenance and
upkeep?”
- “I am in support of it if farmers are promoting it - given that
they’re the experts in that field and it’s their economy that may be
impacted.”
- Concerns or skepticism: specific worries about
chemicals, crop or animal welfare, energy reliability, or outright doubt
- “I would be worried about pieces of the solar panels or chemicals
leaking into the soil where crops are grown and animals graze.”
- “My initial reaction is why would you put solar panels above crops
when crops need sunshine to grow; creating the mechanisms to allow the
panels to move around seems overly complicated. Why not just build them
over parking lots or areas that need to be shaded?”
Coding followed an LLM-assisted process. A large language model
(Claude, Anthropic) generated initial category assignments for all 59
non-empty responses from retained participants. The author then reviewed
each assignment independently, recording agreement or disagreement.
Initial agreement was 71.2% (42 of 59 responses). The 17 disagreements
were resolved in a single reconciliation round in which the author
assigned final categories, which were treated as authoritative. One
response was uncodeable (e.g., “You didn’t say what to type here.”) and
was excluded from frequency analyses, yielding a final analytic sample
of 58 coded responses.
open_coded <- read.csv("open_coding_final.csv")
open_coded <- subset(open_coded, Category != "Uncodeable")
theme_tab <- as.data.frame(table(open_coded$Category))
colnames(theme_tab) <- c("Theme", "n")
theme_tab$pct <- round(100 * theme_tab$n / sum(theme_tab$n), 1)
theme_tab <- theme_tab[order(theme_tab$n, decreasing = FALSE), ]
par(mar = c(4, 16, 3, 5))
bp <- barplot(theme_tab$n,
names.arg = str_wrap(theme_tab$Theme, width = 20),
horiz = TRUE,
las = 1,
xlab = "Count",
main = "Initial Impression Themes",
cex.main = 1.3,
col = "grey75",
border = "black",
xlim = c(0, max(theme_tab$n) * 1.6))
text(x = theme_tab$n + 0.5,
y = bp,
labels = paste0("n = ", theme_tab$n, " (", theme_tab$pct, "%)"),
adj = 0,
cex = 0.9)
Specific Advantages and Concerns (Open Response)
After their initial impressions, participants were asked: “What
specific advantages or concerns, if any, come to mind regarding
agrivoltaic farming?”
Responses were coded using the same LLM-assisted process as the
initial impressions question. Claude (Anthropic) generated initial
category assignments separately for advantages and concerns mentioned in
each response. The author reviewed all assignments, and disagreements
were resolved in a single reconciliation round with the author’s
judgment treated as authoritative. Two coding decisions were added
during reconciliation: a “Several reasons given” category for responses
that listed multiple distinct advantages or concerns that could not be
meaningfully collapsed into one label, and a “None mentioned” category
for responses that did not offer a specific advantage or concern on a
given dimension.
specific <- read.csv("specific_coding_final.csv")
adv_tab <- as.data.frame(table(specific$Advantage_Category))
colnames(adv_tab) <- c("Category", "n")
adv_tab$pct <- round(100 * adv_tab$n / sum(adv_tab$n), 1)
adv_tab <- adv_tab[order(adv_tab$n, decreasing = FALSE), ]
con_tab <- as.data.frame(table(specific$Concern_Category))
colnames(con_tab) <- c("Category", "n")
con_tab$pct <- round(100 * con_tab$n / sum(con_tab$n), 1)
con_tab <- con_tab[order(con_tab$n, decreasing = FALSE), ]
par(mfrow = c(2, 1), mar = c(4, 20, 3, 10))
bp_adv <- barplot(adv_tab$n,
names.arg = str_wrap(adv_tab$Category, width = 25),
horiz = TRUE,
las = 1,
xlab = "Count",
main = "Advantages Mentioned",
cex.main = 1.3,
col = "grey75",
border = "black",
xlim = c(0, max(adv_tab$n) * 2.2))
text(x = adv_tab$n + 0.3,
y = bp_adv,
labels = paste0("n = ", adv_tab$n, " (", adv_tab$pct, "%)"),
adj = 0,
cex = 0.85)
bp_con <- barplot(con_tab$n,
names.arg = str_wrap(con_tab$Category, width = 25),
horiz = TRUE,
las = 1,
xlab = "Count",
main = "Concerns Mentioned",
cex.main = 1.3,
col = "grey75",
border = "black",
xlim = c(0, max(con_tab$n) * 2.2))
text(x = con_tab$n + 0.3,
y = bp_con,
labels = paste0("n = ", con_tab$n, " (", con_tab$pct, "%)"),
adj = 0,
cex = 0.85)
The relatively high proportion of “none mentioned” responses and the
prevalence of brief, general reactions could reflect several things. It
is possible that many participants simply had little prior knowledge to
draw on, making unprompted elaboration difficult. Participants were not
terribly specific in this open-ended question overall, though they were
slightly more specific at times with their concerns.
Prior Familiarity with Agrivoltaics
fam_levels <- c("Not familiar at all", "Slightly familiar",
"Moderately familiar", "Very familiar", "Extremely familiar")
fam <- factor(dat$prior.familiarity, levels = fam_levels)
par(mar = c(4, 15, 3, 2))
fam_tab <- table(fam[!is.na(fam)])
names(fam_tab) <- str_wrap(names(fam_tab), width = 25)
barplot(fam_tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = "Prior Familiarity with Agrivoltaics",
cex.main = 1.6,
col = "grey75",
border = "black",
xlim = c(0, max(fam_tab) * 1.1))
The data above support the hypothesis that participants had low
familiarity with agrivoltaics prior to the study. Some respondents may
also have provided low-effort responses due to the open-ended format
itself, or because they wanted to finish the study and get paid. It is
also worth noting that the initial impressions question may have
captured most of what participants had to say, leaving little new ground
for the follow-up prompt. Disentangling these possibilities would
require additional data.
Attitudes toward agrivoltaic systems
Attitudes towards the agrivoltaic system described in this study were
generally positive.
Item-Level Distributions
library(stringr)
library(psych)
agree_levels <- c("Strongly disagree", "Somewhat disagree",
"Neither agree nor disagree", "Somewhat agree", "Strongly agree")
reverse_recode <- function(x, levels = agree_levels) {
factor(levels[length(levels) + 1 - as.numeric(factor(x, levels = levels))],
levels = levels)
}
dat$gen.1 <- factor(dat$gen.1, levels = agree_levels)
dat$gen.2 <- factor(dat$gen.2, levels = agree_levels)
dat$gen.3 <- factor(dat$gen.3, levels = agree_levels)
dat$gen.4.R <- factor(dat$gen.4.R, levels = agree_levels) # raw, not reverse scored
dat$gen.5 <- factor(dat$gen.5, levels = agree_levels)
gen_df <- data.frame(
gen.1 = as.numeric(dat$gen.1),
gen.2 = as.numeric(dat$gen.2),
gen.3 = as.numeric(dat$gen.3),
gen.4.R = as.numeric(reverse_recode(dat$gen.4.R)), # reverse scored for EFA/composites
gen.5 = as.numeric(dat$gen.5)
)
gen_items <- list(
gen.1 = "I find the idea of agrivoltaic farming appealing.",
gen.2 = "Agrivoltaic systems seem like a practical use of farmland.",
gen.3 = "This approach seems innovative.",
gen.4.R = "This approach seems risky. (raw, not reverse scored)",
gen.5 = "I would support farmers adopting this system in my region."
)
par(mfrow = c(5, 1), mar = c(4, 18, 3, 2))
for (col in names(gen_items)) {
x <- dat[[col]]
tab <- table(factor(x, levels = agree_levels))
names(tab) <- str_wrap(names(tab), width = 20)
barplot(tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = str_wrap(gen_items[[col]], width = 60),
cex.main = 1.3,
col = "grey75",
border = "black",
xlim = c(0, max(tab) * 1.1))
}
Exploratory Factor Analysis
An exploratory factor analysis was conducted to examine the factor
structure and factor loadings of the 5 attitude questions.
fa.parallel(gen_df, fa = "fa", main = "Scree Plot: General Attitudes")
efa_gen <- fa(gen_df, nfactors = 1, rotate = "oblimin", fm = "ml")
loadings_df <- data.frame(
Item = c("I find the idea of agrivoltaic farming appealing.",
"Agrivoltaic systems seem like a practical use of farmland.",
"This approach seems innovative.",
"This approach seems risky. (reverse scored)",
"I would support farmers adopting this system in my region."),
Loading = round(efa_gen$loadings[, 1], 3)
)
knitr::kable(loadings_df, row.names = FALSE,
caption = "Table 5. Factor loadings for general attitude items (ML1).")
Table 5. Factor loadings for general attitude items
(ML1).
| I find the idea of agrivoltaic farming appealing. |
0.882 |
| Agrivoltaic systems seem like a practical use of
farmland. |
0.952 |
| This approach seems innovative. |
0.847 |
| This approach seems risky. (reverse scored) |
0.672 |
| I would support farmers adopting this system in my
region. |
0.773 |
The parallel analysis suggested a single-factor solution, and the EFA
supported this. All five items loaded strongly on a single factor
(ML1), with loadings ranging from 0.672 (“seems risky,” reverse
scored) to 0.952 (“practical use of farmland”). The single factor
accounted for 69% of the variance in responses. Fit indices were mixed:
the model showed good fit on some indices (RMSR = 0.061, fit based on
off-diagonal values = 0.992) but the RMSEA was high (0.195), and the
likelihood chi-square test was significant (p = .006),
suggesting the single-factor model may not fully capture the structure
of these items. (Though this particular chi-sqhare test is known to be
overly sensitive). Given the small sample size (n = 59 with
complete data), these results should be interpreted cautiously. The
overall pattern is consistent with the items reflecting a general
positive orientation toward agrivoltaics, though replication in a larger
sample is warranted.
Perceived Naturalness of Agrivoltaic Produce
Item-Level Distributions
Perceived naturaleness of agrivoltaic produce was generally high.
dat$natural.1 <- factor(dat$natural.1, levels = agree_levels)
dat$natural.2 <- factor(dat$natural.2, levels = agree_levels)
dat$natural.3 <- factor(dat$natural.3, levels = agree_levels)
dat$natural.4.R <- factor(dat$natural.4.R, levels = agree_levels) # raw, not reverse scored
natural_df <- data.frame(
natural.1 = as.numeric(dat$natural.1),
natural.2 = as.numeric(dat$natural.2),
natural.3 = as.numeric(dat$natural.3),
natural.4.R = as.numeric(reverse_recode(dat$natural.4.R)) # reverse scored for EFA/composites
)
natural_items <- list(
natural.1 = "Produce grown using agrivoltaic methods seems natural to me.",
natural.2 = "I would feel safe eating produce grown under solar panels.",
natural.3 = "I would expect produce grown using agrivoltaic methods to be high quality.",
natural.4.R = "Growing food under solar panels seems like it could harm the produce. (raw, not reverse scored)"
)
par(mfrow = c(4, 1), mar = c(4, 18, 3, 2))
for (col in names(natural_items)) {
x <- dat[[col]]
tab <- table(factor(x, levels = agree_levels))
names(tab) <- str_wrap(names(tab), width = 20)
barplot(tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = str_wrap(natural_items[[col]], width = 60),
cex.main = 1.3,
col = "grey75",
border = "black",
xlim = c(0, max(tab) * 1.1))
}
Perceived naturalness of agrivoltaic produce was generally high,
though somewhat lower than general attitudes overall. Responses to the
first two items — perceived naturalness and food safety — were notably
positive. The latter two items — expected quality and potential for harm
— elicited more ambivalent responses, with a higher proportion of
“Neither agree nor disagree” selections. This pattern suggests the scale
may capture two related but distinct facets of perceived naturalness: a
general sense that agrivoltaic produce seems natural and safe, and a
more uncertain appraisal of specific quality and harm outcomes for which
participants may simply lack the knowledge to form confident
judgments.
Exploratory Factor Analysis
Another EFA was conducted to examine the factor structure and factor
loadings of these items.
fa.parallel(natural_df, fa = "fa", main = "Scree Plot: Perceived Naturalness")
efa_natural <- fa(natural_df, nfactors = 1, rotate = "oblimin", fm = "ml")
natural_loadings_df <- data.frame(
Item = c("Produce grown using agrivoltaic methods seems natural to me.",
"I would feel safe eating produce grown under solar panels.",
"I would expect produce grown using agrivoltaic methods to be high quality.",
"Growing food under solar panels seems like it could harm the produce. (reverse scored)"),
Loading = round(efa_natural$loadings[, 1], 3)
)
knitr::kable(natural_loadings_df, row.names = FALSE,
caption = "Table 6. Factor loadings for perceived naturalness items (ML1).")
The parallel analysis suggested a single-factor solution for the
perceived naturalness items. The single factor accounted for 65.1% of
the variance in responses, with loadings ranging from 0.733 to
0.916.
Willingness to Pay a Premium
Participants were asked whether they would be willing to pay more for
broccoli grown using agrivoltaic methods compared to conventionally
grown broccoli (baseline price: $2.00 per head).
Initial Response
Most participants (62.3%) said they would not pay a premium for
agrivoltaic brocoli. A third of participants were unsure, and only 4.3%
said they would pay a premium.
prem_levels <- c("Yes", "I'm not sure", "No")
prem <- factor(dat$premium.initial, levels = prem_levels)
par(mar = c(4, 10, 3, 8))
prem_tab <- table(prem[!is.na(prem)])
prem_pct <- round(100 * prop.table(prem_tab), 1)
bp <- barplot(prem_tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = "Willingness to Pay a Premium",
cex.main = 1.3,
col = "grey75",
border = "black",
xlim = c(0, max(prem_tab) * 1.6))
text(x = prem_tab + 0.5,
y = bp,
labels = paste0("n = ", prem_tab, " (", prem_pct, "%)"),
adj = 0,
cex = 0.9)
Among Those Willing to Pay More
Only 3 participants indicated they would pay a premium. Their stated
amounts per head of broccoli were $2.50, $3.40, and $5.90. Given the
small number of responses, no inferential analysis was conducted on this
item.
Reasons for Declining
no_levels <- c("I don't think it would taste different.",
"I'm not convinced it's better for the environment.",
"I can't afford to pay more for produce.",
"I don't trust this agrivoltaic farming.",
"Other")
prem_no <- factor(dat$premium.no, levels = no_levels)
no_tab <- table(prem_no[!is.na(prem_no)])
no_pct <- round(100 * prop.table(no_tab), 1)
names(no_tab) <- c("Wouldn't taste different",
"Not better for environment",
"Can't afford it",
"Don't trust agrivoltaics",
"Other")
par(mar = c(4, 14, 3, 10))
bp <- barplot(no_tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = "Reasons for Declining to Pay a Premium",
cex.main = 1.0,
cex.names = 0.8,
cex.axis = 0.8,
col = "grey75",
border = "black",
xlim = c(0, max(no_tab) * 2.0))
text(x = no_tab + 0.3,
y = bp,
labels = paste0("n = ", no_tab, " (", no_pct, "%)"),
adj = 0,
cex = 0.75)
Four participants selected “Other” and provided the following
free-text responses:
- “I already don’t like broccoli so why would the production method
matter to me?”
- “I’m not sure it better than organic.”
- “In any case, the product should be cheaper because it’s not grown
normally.”
- “What does one have to with the other?”
These responses suggest that at least some participants expect
agrivoltaic produce to cost less than conventional produce, or
they question the premise that production method should affect price at
all. Though based on very few responses, this pattern may be worth
exploring in future work.
Hypothetical Policy Support
Item-Level Distributions
dat$support.1 <- factor(dat$support.1, levels = agree_levels)
dat$support.2 <- factor(dat$support.2, levels = agree_levels)
dat$support.3.R <- factor(dat$support.3.R, levels = agree_levels) # raw, not reverse scored
support_df <- data.frame(
support.1 = as.numeric(dat$support.1),
support.2 = as.numeric(dat$support.2),
support.3.R = as.numeric(reverse_recode(dat$support.3.R)) # reverse scored for EFA/composites
)
support_items <- list(
support.1 = "I would support pilot programs testing agrivoltaic farming.",
support.2 = "I would support government incentives for this approach if evidence showed benefits.",
support.3.R = "I would oppose agrivoltaic farming being widely adopted. (raw, not reverse scored)"
)
par(mfrow = c(3, 1), mar = c(4, 18, 3, 10))
for (col in names(support_items)) {
x <- dat[[col]]
tab <- table(factor(x, levels = agree_levels))
pct <- round(100 * prop.table(tab), 1)
names(tab) <- str_wrap(names(tab), width = 20)
bp <- barplot(tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = str_wrap(support_items[[col]], width = 60),
cex.main = 1.1,
cex.names = 0.85,
cex.axis = 0.85,
col = "grey75",
border = "black",
xlim = c(0, max(tab) * 2.0))
text(x = tab + 0.3,
y = bp,
labels = paste0("n = ", tab, " (", pct, "%)"),
adj = 0,
cex = 0.75)
}
Exploratory Factor Analysis
fa.parallel(support_df, fa = "fa", main = "Scree Plot: Hypothetical Policy Support")
efa_support <- fa(support_df, nfactors = 1, rotate = "oblimin", fm = "ml")
support_loadings_df <- data.frame(
Item = c("I would support pilot programs testing agrivoltaic farming.",
"I would support government incentives for this approach if evidence showed benefits.",
"I would oppose agrivoltaic farming being widely adopted. (reverse scored)"),
Loading = round(efa_support$loadings[, 1], 3)
)
knitr::kable(support_loadings_df, row.names = FALSE,
caption = "Table 7. Factor loadings for hypothetical policy support items (ML1).")
Table 7. Factor loadings for hypothetical policy support items
(ML1).
| I would support pilot programs testing agrivoltaic
farming. |
0.946 |
| I would support government incentives for this approach
if evidence showed benefits. |
0.828 |
| I would oppose agrivoltaic farming being widely
adopted. (reverse scored) |
0.401 |
The parallel analysis suggested a single-factor solution for the
policy support items. The single factor accounted for 58.1% of the
variance in responses. The first two items — support for pilot programs
and support for government incentives contingent on evidence — loaded
strongly onto the factor (0.946 and 0.828, respectively), suggesting
they tap a coherent latent construct of policy support. The third item,
which asked about opposition to wide adoption, loaded more weakly
(0.401), which may reflect the fact that opposing widespread adoption is
a conceptually distinct judgment from supporting early-stage programs.
One can plausibly endorse pilot testing while remaining uncertain about
broader rollout afterall. It is also worth noting that three items is
the minimum for a psychometrically defensible scale, and the weak
loading of the third item raises questions about whether this composite
fully captures the intended construct. Future iterations of this survey
might benefit from additional items that more directly address
opposition, skepticism about scaling, or conditional support tied to
specific evidence thresholds.
Trust in Science
Item-Level Distributions
dat$trust.sci.1 <- factor(dat$trust.sci.1, levels = agree_levels)
dat$trust.sci.2 <- factor(dat$trust.sci.2, levels = agree_levels)
dat$trust.sci.3 <- factor(dat$trust.sci.3, levels = agree_levels)
dat$trust.sci.4 <- factor(dat$trust.sci.4, levels = agree_levels)
trust_df <- data.frame(
trust.sci.1 = as.numeric(dat$trust.sci.1),
trust.sci.2 = as.numeric(dat$trust.sci.2),
trust.sci.3 = as.numeric(dat$trust.sci.3),
trust.sci.4 = as.numeric(dat$trust.sci.4)
)
trust_items <- list(
trust.sci.1 = "I trust scientists to tell the truth about their research findings.",
trust.sci.2 = "Scientists are honest about what they do and do not know.",
trust.sci.3 = "Scientific research is a reliable way to find out about the world.",
trust.sci.4 = "I believe the results of scientific studies even when they challenge my personal beliefs."
)
par(mfrow = c(4, 1), mar = c(4, 18, 3, 2))
for (col in names(trust_items)) {
x <- dat[[col]]
tab <- table(factor(x, levels = agree_levels))
names(tab) <- str_wrap(names(tab), width = 20)
barplot(tab,
horiz = TRUE,
las = 1,
xlab = "Count",
main = str_wrap(trust_items[[col]], width = 60),
cex.main = 1.3,
col = "grey75",
border = "black",
xlim = c(0, max(tab) * 1.1))
}
Exploratory Factor Analysis
fa.parallel(trust_df, fa = "fa", main = "Scree Plot: Trust in Science")
efa_trust <- fa(trust_df, nfactors = 1, rotate = "oblimin", fm = "ml")
trust_loadings_df <- data.frame(
Item = c("I trust scientists to tell the truth about their research findings.",
"Scientists are honest about what they do and do not know.",
"Scientific research is a reliable way to find out about the world.",
"I believe the results of scientific studies even when they challenge my personal beliefs."),
Loading = round(efa_trust$loadings[, 1], 3)
)
knitr::kable(trust_loadings_df, row.names = FALSE,
caption = "Table 8. Factor loadings for trust in science items (ML1).")
Table 8. Factor loadings for trust in science items
(ML1).
| I trust scientists to tell the truth about their
research findings. |
0.908 |
| Scientists are honest about what they do and do not
know. |
0.930 |
| Scientific research is a reliable way to find out about
the world. |
0.706 |
| I believe the results of scientific studies even when
they challenge my personal beliefs. |
0.766 |
The parallel analysis suggested a single-factor solution for the
trust in science items. The single factor accounted for 69.4% of the
variance in responses, with loadings ranging from 0.706 to 0.93.
Correlational Analyses
Before selecting a correlation method, we examine the bivariate
relationships between all composite scores (with applicable reverse
scoring) and continuous demographic variables for evidence of
non-linearity.
# Composites
dat$gen_composite <- rowMeans(data.frame(
gen.1 = as.numeric(dat$gen.1),
gen.2 = as.numeric(dat$gen.2),
gen.3 = as.numeric(dat$gen.3),
gen.4.R = as.numeric(dat$gen.4.R),
gen.5 = as.numeric(dat$gen.5)
), na.rm = TRUE)
dat$natural_composite <- rowMeans(data.frame(
natural.1 = as.numeric(dat$natural.1),
natural.2 = as.numeric(dat$natural.2),
natural.3 = as.numeric(dat$natural.3),
natural.4.R = as.numeric(dat$natural.4.R)
), na.rm = TRUE)
dat$support_composite <- rowMeans(data.frame(
support.1 = as.numeric(dat$support.1),
support.2 = as.numeric(dat$support.2),
support.3.R = as.numeric(dat$support.3.R)
), na.rm = TRUE)
dat$trust_composite <- rowMeans(data.frame(
trust.sci.1 = as.numeric(dat$trust.sci.1),
trust.sci.2 = as.numeric(dat$trust.sci.2),
trust.sci.3 = as.numeric(dat$trust.sci.3),
trust.sci.4 = as.numeric(dat$trust.sci.4)
), na.rm = TRUE)
dat$politics_num <- as.numeric(factor(dat$politics,
levels = c("Very liberal", "Liberal", "Slightly liberal",
"Neither liberal nor conservative (Moderate)",
"Slightly conservative", "Conservative", "Very conservative")))
dat$age_num <- as.numeric(dat$age)
# Pairs plot
pairs_df <- data.frame(
`General\nAttitudes` = dat$gen_composite,
`Perceived\nNaturalness` = dat$natural_composite,
`Policy\nSupport` = dat$support_composite,
`Trust in\nScience` = dat$trust_composite,
`Conservatism` = dat$politics_num,
`Age` = dat$age_num,
check.names = FALSE
)
pairs(pairs_df,
lower.panel = function(x, y) {
points(x, y, pch = 19, cex = 0.6, col = rgb(0, 0, 0, 0.4))
abline(lm(y ~ x, na.action = na.omit), col = "red", lwd = 1.2)
lines(lowess(x[!is.na(x) & !is.na(y)],
y[!is.na(x) & !is.na(y)]),
col = "blue", lwd = 1.2, lty = 2)
},
upper.panel = function(x, y) {
r <- cor(x, y, use = "pairwise.complete.obs")
text(mean(range(x, na.rm = TRUE)),
mean(range(y, na.rm = TRUE)),
labels = round(r, 2),
cex = 1.2)
},
diag.panel = function(x) {
usr <- par("usr")
par(usr = c(usr[1:2], 0, 1.5))
d <- density(x, na.rm = TRUE)
d$y <- d$y / max(d$y)
lines(d, col = "grey40", lwd = 1.5)
},
gap = 0.3)
legend("topright",
legend = c("Linear fit", "Loess smoother"),
col = c("red", "blue"),
lty = c(1, 2),
lwd = 1.5,
cex = 0.8,
bty = "n")
The plot above reveals strong positive intercorrelations among the
four attitude composites (rs ranging from .61 to .82),
suggesting that respondents who viewed agrivoltaics positively on one
dimension tended to do so across others as well. General attitudes and
policy support had a particularly strong correlation (r = .82),
indicating substantial overlap between these constructs in this sample.
Political orientation showed consistent negative relationships with all
four composites, with more conservative respondents tending to report
less favorable attitudes, lower perceived naturalness, less policy
support, and lower trust in science. These relationships appeared
roughly linear. Relationships involving age were inconsistent and the
loess smoother (blue dashed line) diverged notably from the linear fit
(solid red line) across several pairs, suggesting age relationships
should be interpreted with caution given the sample size. The attitude
composites were all left-skewed, with most respondents clustered at the
positive end of each scale. Given the skewed distributions and the
ordinal nature of the underlying items, Spearman correlations were used
for all subsequent analyses.
Spearman Correlation Matrix
library(corrplot)
cor_vars <- data.frame(
`General Attitudes` = dat$gen_composite,
`Perc. Naturalness` = dat$natural_composite,
`Policy Support` = dat$support_composite,
`Trust in Science` = dat$trust_composite,
`Conservatism` = dat$politics_num,
`Age` = dat$age_num,
check.names = FALSE
)
cor_matrix <- cor(cor_vars, method = "spearman", use = "pairwise.complete.obs")
n_vars <- ncol(cor_vars)
p_matrix <- matrix(0, n_vars, n_vars)
for (i in 1:n_vars) {
for (j in 1:n_vars) {
if (i != j) {
test <- cor.test(cor_vars[[i]], cor_vars[[j]],
method = "spearman", exact = FALSE)
p_matrix[i, j] <- test$p.value
}
}
}
rownames(p_matrix) <- colnames(cor_matrix)
colnames(p_matrix) <- colnames(cor_matrix)
# Replace non-significant coefficients with NA so they show as blank
cor_display <- cor_matrix
cor_display[p_matrix >= 0.05] <- NA
corrplot(cor_matrix,
method = "color",
type = "lower",
tl.col = "black",
tl.srt = 45,
tl.cex = 0.9,
diag = FALSE,
p.mat = p_matrix,
sig.level = 0.05,
insig = "blank",
addCoef.col = "black",
number.cex = 0.8,
col = COL2("RdBu", 200))
Non-significant correlations (p >= .05) are shown in
white with no coefficient displayed. Color intensity and direction
reflect the magnitude and sign of the Spearman correlation coefficient.
Given the exploratory nature of this pilot and the small sample size,
these results should be interpreted as preliminary and
hypothesis-generating rather than confirmatory.
Demographic Predictors of Key Outcomes
The following analyses examine whether demographic variables predict
key attitude outcomes and whether any of them moderate the relationship
between conservatism and policy support. Given the small sample size,
all results should be treated as exploratory and underpowered. They are
included here for completeness and to generate hypotheses for future
work.
Click to view demographic predictor analyses
Visualizing Conservatism Interactions
Before modeling, we examine whether the relationship between
conservatism and key outcomes varies across education, farm background,
income, and importance of agriculture in the community.
library(dplyr)
library(ggplot2)
library(patchwork)
dat$edu_collapsed <- case_when(
dat$education %in% c("Less than high school",
"High school diploma or GED",
"2 year degree",
"Some college") ~ "No 4-year degree",
dat$education == "4 year degree" ~ "4-year degree",
dat$education %in% c("Master's degree",
"Doctoral or professional degree") ~ "Graduate degree",
TRUE ~ NA_character_
)
dat$edu_collapsed <- factor(dat$edu_collapsed,
levels = c("No 4-year degree",
"4-year degree",
"Graduate degree"))
dat$income_clean <- ifelse(dat$income == "Prefer not to say", NA, dat$income)
dat$income_num <- as.numeric(factor(dat$income_clean,
levels = c("Under $25,000", "$25,000-$49,999", "$50,000-$74,999",
"$75,000-$99,999", "$100,000-$149,999", "$150,000+"),
ordered = TRUE))
dat$rural_collapsed <- case_when(
dat$rural %in% c("Yes, I grew up on a farm",
"Yes, I grew up in a rural area where farming was common") ~ "Farm/rural background",
dat$rural == "No, but I have close family or friends who farm" ~ "Farming connections",
dat$rural == "No connections to farming" ~ "No connections",
TRUE ~ NA_character_
)
dat$rural_collapsed <- factor(dat$rural_collapsed,
levels = c("No connections",
"Farming connections",
"Farm/rural background"))
dat$ag_importance_num <- as.numeric(factor(dat$ag.importance,
levels = c("Not at all important", "Slightly important",
"Moderately important", "Very important",
"Extremely important"),
ordered = TRUE))
plot_interaction <- function(group_var, group_label, outcome_var, outcome_label) {
df <- data.frame(
x = dat$politics_num,
y = dat[[outcome_var]],
group = dat[[group_var]]
)
df <- df[!is.na(df$x) & !is.na(df$y) & !is.na(df$group), ]
ggplot(df, aes(x = x, y = y, color = group)) +
geom_point(alpha = 0.5, size = 2.5) +
geom_smooth(method = "lm", se = TRUE, linewidth = 1) +
scale_x_continuous(breaks = 1:7,
labels = c("Very\nliberal", "Liberal", "Slightly\nliberal",
"Moderate", "Slightly\nconservative",
"Conservative", "Very\nconservative")) +
labs(x = "Political Orientation", y = outcome_label, color = group_label) +
theme_classic(base_size = 14) +
theme(legend.position = "bottom",
legend.title = element_text(face = "bold", size = 13),
legend.text = element_text(size = 12),
axis.text = element_text(size = 11),
axis.title = element_text(size = 13))
}
p1 <- plot_interaction("edu_collapsed", "Education", "support_composite", "Policy Support")
p2 <- plot_interaction("rural_collapsed", "Farm Background", "support_composite", "Policy Support")
p3 <- plot_interaction("edu_collapsed", "Education", "gen_composite", "General Attitudes")
p4 <- plot_interaction("rural_collapsed", "Farm Background", "gen_composite", "General Attitudes")
p1 / p2 / p3 / p4
Main Effects Regression
library(broom)
dat$edu_num <- as.numeric(factor(dat$education,
levels = c("Less than high school", "High school diploma or GED",
"Some college", "2 year degree", "4 year degree",
"Master's degree", "Doctoral or professional degree"),
ordered = TRUE))
dat$rural_num <- as.numeric(factor(dat$rural,
levels = c("No connections to farming",
"No, but I have close family or friends who farm",
"Yes, I grew up in a rural area where farming was common",
"Yes, I grew up on a farm"),
ordered = TRUE))
m_main <- lm(support_composite ~ politics_num + edu_num +
income_num + rural_num + ag_importance_num,
data = dat)
coef_df <- tidy(m_main, conf.int = TRUE) %>%
filter(term != "(Intercept)") %>%
mutate(term = recode(term,
"politics_num" = "Conservatism",
"edu_num" = "Education",
"income_num" = "Income",
"rural_num" = "Farm background",
"ag_importance_num" = "Ag importance in community"
),
significant = p.value < 0.05)
ggplot(coef_df, aes(x = estimate, y = reorder(term, estimate),
color = significant)) +
geom_vline(xintercept = 0, linetype = "dashed", color = "grey50") +
geom_errorbarh(aes(xmin = conf.low, xmax = conf.high),
height = 0.2, linewidth = 0.8) +
geom_point(size = 3) +
scale_color_manual(values = c("FALSE" = "grey60", "TRUE" = "#2C6BAC"),
labels = c("FALSE" = "p >= .05", "TRUE" = "p < .05"),
name = NULL) +
labs(x = "Coefficient estimate (95% CI)",
y = NULL,
title = "Main Effects on Policy Support") +
theme_classic(base_size = 13) +
theme(legend.position = "bottom")
This study is underpowered, so it is too soon to conclude the first 4
variables are unrelated to policy support. There was a statistically
significant tendency for people to support agrivoltaics less as a
function of how conservative they are.
Interaction Models
To explore whether the relationship between conservatism and policy
support varied across demographic subgroups, we tested four separate
interaction models, each adding a single conservatism x demographic term
(education, income, farm background, and agricultural importance) to the
main effects model. Given the small sample size and purely exploratory
intent, no corrections for multiple comparisons were applied. None of
the interaction terms approached significance, suggesting no reliable
evidence in this pilot sample that demographic variables moderate the
conservatism-support relationship. Full model output is available
below.
interaction_vars <- list(
edu_num = "Education",
income_num = "Income",
rural_num = "Farm Background",
ag_importance_num = "Ag Importance"
)
for (var in names(interaction_vars)) {
fml <- as.formula(paste("support_composite ~ politics_num *", var))
m <- lm(fml, data = dat)
cat("\n\n####", interaction_vars[[var]], "x Conservatism\n\n")
print(knitr::kable(round(summary(m)$coefficients, 3),
caption = paste("Outcome: Policy Support ~",
interaction_vars[[var]], "x Conservatism")))
}
Education x Conservatism
Outcome: Policy Support ~ Education x Conservatism
| (Intercept) |
3.622 |
0.512 |
7.077 |
0.000 |
| politics_num |
-0.117 |
0.144 |
-0.810 |
0.421 |
| edu_num |
0.053 |
0.111 |
0.482 |
0.632 |
| politics_num:edu_num |
-0.006 |
0.030 |
-0.201 |
0.841 |
Income x Conservatism
Outcome: Policy Support ~ Income x Conservatism
| (Intercept) |
3.785 |
0.317 |
11.935 |
0.000 |
| politics_num |
-0.116 |
0.090 |
-1.298 |
0.200 |
| income_num |
0.020 |
0.087 |
0.229 |
0.820 |
| politics_num:income_num |
-0.006 |
0.023 |
-0.250 |
0.804 |
Farm Background x Conservatism
Outcome: Policy Support ~ Farm Background x
Conservatism
| (Intercept) |
3.631 |
0.322 |
11.275 |
0.000 |
| politics_num |
-0.041 |
0.090 |
-0.454 |
0.651 |
| rural_num |
0.121 |
0.169 |
0.720 |
0.475 |
| politics_num:rural_num |
-0.055 |
0.045 |
-1.224 |
0.226 |
Ag Importance x Conservatism
Outcome: Policy Support ~ Ag Importance x
Conservatism
| (Intercept) |
3.780 |
0.369 |
10.235 |
0.000 |
| politics_num |
-0.052 |
0.104 |
-0.503 |
0.617 |
| ag_importance_num |
0.022 |
0.130 |
0.167 |
0.868 |
| politics_num:ag_importance_num |
-0.031 |
0.035 |
-0.875 |
0.386 |