Introduction

Agrivoltaics combines solar energy production and agriculture on the same land by mounting solar panels above crops or pasture. Early research suggests benefits for certain crops and energy output, though costs are higher and crop performance varies by species. Public perception matters because even well-performing systems may face adoption barriers if consumers distrust the produce or oppose the land use. This pilot survey (n = 59) examines attitudes toward agrivoltaic farming, perceived naturalness of agrivoltaic produce, willingness to pay a price premium, and hypothetical policy support.

(Technical note: R code is included throughout for transparency and as a learning resource. Feel free to ignore it if you’re just here for the results.)

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")
Figure 1. Participant demographics.

Figure 1. Participant demographics.

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)
Figure 2. Themes in initial open-ended responses (n = 58).

Figure 2. Themes in initial open-ended responses (n = 58).

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)
Figure 3. Advantages and concerns mentioned in open-ended responses (n = 58).

Figure 3. Advantages and concerns mentioned in open-ended responses (n = 58).

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))
Figure 2. Prior familiarity with agrivoltaic farming.

Figure 2. Prior familiarity with agrivoltaic farming.

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))
}
Figure 4. General attitude item distributions.

Figure 4. General attitude item distributions.

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")
Figure 4. Scree plot for general attitude items.

Figure 4. Scree plot for general attitude items.

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).
Item Loading
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))
}
Figure 5. Perceived naturalness item distributions.

Figure 5. Perceived naturalness item distributions.

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")
Figure 6. Scree plot for perceived naturalness items.

Figure 6. Scree plot for perceived naturalness items.

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)
Figure 7. Willingness to pay a premium for agrivoltaically grown broccoli.

Figure 7. Willingness to pay a premium for agrivoltaically grown broccoli.

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)
Figure 8. Reasons for declining to pay a premium.

Figure 8. Reasons for declining to pay a premium.

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)
}
Figure 9. Hypothetical policy support item distributions.

Figure 9. Hypothetical policy support item distributions.

Exploratory Factor Analysis

fa.parallel(support_df, fa = "fa", main = "Scree Plot: Hypothetical Policy Support")
Figure 10. Scree plot for hypothetical policy support items.

Figure 10. Scree plot for hypothetical policy support items.

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).
Item Loading
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))
}
Figure 11. Trust in science item distributions.

Figure 11. Trust in science item distributions.

Exploratory Factor Analysis

fa.parallel(trust_df, fa = "fa", main = "Scree Plot: Trust in Science")
Figure 12. Scree plot for trust in science items.

Figure 12. Scree plot for trust in science items.

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).
Item Loading
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")
Figure 13. Pairwise scatterplots of composite scores and continuous demographics.

Figure 13. Pairwise scatterplots of composite scores and continuous demographics.

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))
Figure 14. Spearman correlation matrix.

Figure 14. Spearman correlation matrix.

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
Figure 15. Conservatism vs. policy support by demographic subgroups.

Figure 15. Conservatism vs. policy support by demographic subgroups.

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")
Figure 16. Main effects of demographic predictors on policy support.

Figure 16. Main effects of demographic predictors on policy support.

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
Estimate Std. Error t value Pr(>|t|)
(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
Estimate Std. Error t value Pr(>|t|)
(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
Estimate Std. Error t value Pr(>|t|)
(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
Estimate Std. Error t value Pr(>|t|)
(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