cvpa gives you weighted party support and policy-issue attitudes by demographic group, over time, from Canadian public-opinion surveys going back to 1945. As of version 0.2.0, that covers several hundred policy questions across seven issue domains – economy and taxation, energy and environment, health/education/social programs, immigration/diversity/ federalism, foreign policy and identity, social issues/rights/justice, and democracy and institutions – in addition to vote choice and intention. From cvpa’s point of view, vote is just one more question: it’s decoded and analyzed the same way as any policy question.
There are two separate paths through the package, and it’s worth being clear about which one you’re on:
update_cvpa_data(), question_catalog(),
question_data(), wtd_response(),
response_gap_analysis()) reads Parquet files hosted
alongside the policy-attitudes
web app, covering every question in the catalog. This is almost
certainly what you want for new analysis.vote_data,
wtd_vote(), gap_analysis()) is the original
vote-choice/intention dataset and functions the package shipped with
before 0.2.0, kept as-is for reproducing prior work (in particular Lucas, Romualdi,
Armstrong, and Goodyear-Grant (2025) on the Canadian Vote Intention
Dataset, Canadian Journal of Political Science 58 (3): 666-80).
wtd_vote()/gap_analysis() are, under the hood,
thin wrappers around the same general engine that powers the live data
path, so their behavior hasn’t changed – see the last section of this
vignette.This vignette focuses on the live data path, since that’s what’s new.
cvpa doesn’t bundle policy-attitude data in the package itself –
there’s too much of it, and it’s updated periodically as new survey
waves are added. Instead, update_cvpa_data() downloads what
you ask for into a local cache (see cvpa_cache_dir()) the
first time you need it, and every other function reads only from that
cache. Nothing in the package other than update_cvpa_data()
ever touches the network, so you’re always in control of when a download
happens.
The question catalog and the demographic/party code-labels table are
small and are always refreshed by update_cvpa_data().
Respondent-level data for a specific question is only fetched when you
name it (there are several hundred questions, so there’s deliberately no
“fetch everything” option – the full dataset is available as
dataset.parquet from the cvpa_dataset
repository):
question_catalog() lists every available question, one
row per question, deduplicated across the several hundred survey waves
each question has accumulated over the decades:
head(question_catalog(domain = "econ"), 10)
#> question domain domain_label issue_label
#> 1 env_econ_061 econ Economy and Taxation Agree free trade Can-US-Mexico
#> 2 env_econ_074 econ Economy and Taxation Appr cutting federal taxes
#> 3 env_econ_056 econ Economy and Taxation Appr fed govt: deficit reduction
#> 4 env_econ_057 econ Economy and Taxation Appr fed govt: taxation
#> 5 env_econ_060 econ Economy and Taxation Appr prov govt: agriculture
#> 6 env_econ_075 econ Economy and Taxation Appr prov govt: deficit reduction
#> 7 env_econ_049 econ Economy and Taxation Appr prov govt: econ development
#> 8 env_econ_046 econ Economy and Taxation Appr prov govt: privatization
#> 9 env_econ_073 econ Economy and Taxation Appr prov govt: taxation
#> 10 env_econ_058 econ Economy and Taxation Approve GST
#> question_wording
#> 1 Do you agree or disagree that there should be free trade between Canada, the U.S. and Mexico?
#> 2 Do you approve or disapprove of the federal government making a tax cut?
#> 3 Do you approve or disapprove of the way the current federal government is handling deficit reduction?
#> 4 Generally speaking, do you approve or disapprove of the way the current federal government is handling taxation?
#> 5 Do you approve or disapprove of the way your provincial government is handling agriculture and farming?
#> 6 Generally speaking, do you approve or disapprove of the way your current provincial government is handling deficit reduction?
#> 7 Generally speaking, do you approve or disapprove of the way your current provincial government is handling economic development?
#> 8 Do you approve or disapprove of the way your current provincial government is handling privatization?
#> 9 Generally speaking, do you approve or disapprove of the way your current provincial government is handling taxation?
#> 10 As you may know, the federal government has a tax on most goods and services, known as the Goods and Services Tax or GST. Do you approve or disapprove of this tax?
#> n_waves type value_labels
#> 1 9 b Disagree, Agree
#> 2 9 b Disapprove, Approve
#> 3 10 b Disapprove, Approve
#> 4 10 b Disapprove, Approve
#> 5 25 b Disapprove, Approve
#> 6 1 b Disapprove, Approve
#> 7 17 b Disapprove, Approve
#> 8 5 b Disapprove, Approve
#> 9 35 b Disapprove, Approve
#> 10 9 b Disapprove, Approvedomain and type ("b" for
binary questions, "lsm" for 3-point less/same/more-style
questions, "nsa" for 3-point never/sometimes/always- style
questions, or "party" for vote choice/intention) both
filter the catalog if you want to narrow it down. With no arguments,
question_catalog() returns everything, vote included:
nrow(question_catalog())
#> [1] 630
table(question_catalog()$domain)
#>
#> dpin econ ener hwps idim intd soim vote
#> 34 139 57 85 118 72 123 2Once you’ve picked a question,
question_response_labels() shows you what its response
categories actually are and how they’re coded:
question_data() reads the (now cached) respondent-level
data for one question and, by default, decodes both the response and
every demographic column into labeled factors:
d <- question_data("bri_econ_013")
head(d)
#> # A data frame: 6 × 18
#> response_id survey_id response weight year vote_intention vote_choice region
#> * <chr> <chr> <fct> <dbl> <dbl> <fct> <fct> <fct>
#> 1 CES2015_Com… 2015.dta More 0.835 2015 Undecided PC/Conserv… Ontar…
#> 2 2019+Canadi… 2019.dta Less 1.35 2019 PC/Conservati… <NA> Prair…
#> 3 2021 Canadi… 2021.dta More 0.546 2021 Liberal Bloc Quebe… Quebec
#> 4 ces2004-6-8… 2008.dta Same 1.31 2008 <NA> Green Prair…
#> 5 2021 Canadi… 2021.dta Same 0.631 2021 PC/Conservati… PC/Conserv… Prair…
#> 6 2021 Canadi… 2021.dta Less 1.94 2021 PC/Conservati… PC/Conserv… Prair…
#> # ℹ 10 more variables: province <fct>, com_500 <fct>, com_100 <fct>,
#> # gender <fct>, age_cats <fct>, religion <fct>, degree <fct>, language <fct>,
#> # union_household <fct>, occupation <fct>Every question’s data has the same shape: response,
weight, year, plus the demographic and vote
columns listed in cvpa_grouping_vars – see
demographic_vars for what each demographic variable
measures and what categories it takes:
cvpa_grouping_vars
#> [1] "vote_intention" "vote_choice" "region" "province"
#> [5] "com_500" "com_100" "gender" "age_cats"
#> [9] "religion" "degree" "language" "union_household"
#> [13] "occupation"
demographic_vars
#> variable
#> 1 region
#> 2 province
#> 3 com_500
#> 4 com_100
#> 5 gender
#> 6 age_cats
#> 7 religion
#> 8 degree
#> 9 language
#> 10 union_household
#> 11 occupation
#> description
#> 1 Region of residence
#> 2 Province or territory of residence
#> 3 Community size, split at 500,000 population
#> 4 Community size, split at 100,000 population
#> 5 Respondent gender. Binary Man/Woman only -- a non-binary/other response option exists in some surveys from 2019 onward but is too sparse to include here; see the Canadian Opinion Research Archive (https://www.queensu.ca/cora/) for the full data.
#> 6 Age category
#> 7 Religious affiliation
#> 8 University degree attainment
#> 9 Language spoken
#> 10 Union membership in the household
#> 11 Respondent's occupation
#> categories
#> 1 Atlantic; Quebec; Ontario; Prairies; British Columbia; North
#> 2 NL; PEI; NS; NB; QC; ON; MB; SK; AB; BC; YT; NT; NU
#> 3 Population <= 500k; Population > 500k
#> 4 Population <= 100k; Population > 100k
#> 5 Man; Woman
#> 6 18-29; 30-49; 50+
#> 7 Catholic; Protestant; Jewish; Other; None
#> 8 No University Degree; University Degree
#> 9 French; English; French and English; Other
#> 10 No Union Member in Household; Union Member in Household
#> 11 Managers, Executives; Professional; Routine Non-manual; Working Class; Armed Forces; Farmer/Fiserman; Retired; Unemployed; Student; OtherNotice that vote_intention and vote_choice
are in that list alongside the demographics – every question’s data
carries how each respondent voted, so you can group any policy question
by vote just as easily as by gender or region.
response_gap_analysis() generalizes
gap_analysis() to any question: a difference-of-proportions
test between two levels of a grouping variable, for each response
category, for each year:
response_gap_analysis(d, grouping_var = "gender")
#> # A tibble: 27 × 13
#> # Groups: year [9]
#> year response cnt_Man cnt_Woman n_Man n_Woman flag estimate1 estimate2
#> <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
#> 1 2000 Less 118. 63.5 1651. 1697. p-val rel… 0.0715 0.0374
#> 2 2000 Same 315. 222. 1651. 1697. p-val rel… 0.191 0.131
#> 3 2000 More 1218. 1411. 1651. 1697. p-val rel… 0.738 0.832
#> 4 2004 Less 134. 63.5 1991. 2054. p-val rel… 0.0675 0.0309
#> 5 2004 Same 372. 276. 1991. 2054. p-val rel… 0.187 0.134
#> 6 2004 More 1485. 1714. 1991. 2054. p-val rel… 0.745 0.835
#> 7 2006 Less 62.5 19.6 956. 914. p-val rel… 0.0653 0.0215
#> 8 2006 Same 177. 107. 956. 914. p-val rel… 0.185 0.117
#> 9 2006 More 716. 788. 956. 914. p-val rel… 0.749 0.862
#> 10 2008 Less 94.0 61.8 1609. 1665. p-val rel… 0.0584 0.0371
#> # ℹ 17 more rows
#> # ℹ 4 more variables: p.value <dbl>, conf.low <dbl>, conf.high <dbl>,
#> # diff <dbl>The flag column tells you whether every cell going into
that year’s test had an expected count above 5
("p-val reliable") or not ("p-val unreliable")
– see make_flag().
Because vote choice and intention live in the same catalog and the
same resp/question=<code>/data_0.parquet layout as
every policy question, everything above works identically for
"vote_choice"/"vote_intention":
update_cvpa_data(questions = "vote_choice")
#> Fetching question catalog (vbl_data.parquet)...
#> Fetching code labels (code_labels.parquet)...
#> Fetching 1 question file(s)...
#> [1/1] vote_choice
vc <- question_data("vote_choice", years = 2000:2021)
wtd_response(vc, grouping_vars = "gender")
#> # A tibble: 120 × 5
#> year response gender count response_share
#> <dbl> <fct> <fct> <dbl> <dbl>
#> 1 2000 Liberal Man 1218. 0.415
#> 2 2000 Liberal Woman 1365. 0.456
#> 3 2000 PC/Conservative Man 408. 0.139
#> 4 2000 PC/Conservative Woman 414. 0.138
#> 5 2000 CCF/NDP Man 265. 0.0904
#> 6 2000 CCF/NDP Woman 339. 0.113
#> 7 2000 Bloc Quebecois Man 304. 0.104
#> 8 2000 Bloc Quebecois Woman 298. 0.0997
#> 9 2000 Reform/Alliance Man 670. 0.228
#> 10 2000 Reform/Alliance Woman 520. 0.174
#> # ℹ 110 more rowsIf you’re working with the original vote_data dataset –
for instance to reproduce results from the paper it was built for (Lucas, Romualdi,
Armstrong, and Goodyear-Grant (2025)) – wtd_vote() and
gap_analysis() are unchanged:
data(vote_data)
wtd_vote(vote_data, "intention", years = 2000:2022, grouping_vars = "gender")
#> The following years are not available: 2012, 2013, 2014, 2016, 2017, 2018
#> # A tibble: 200 × 5
#> year vote gender count party_support
#> <dbl> <fct> <fct> <dbl> <dbl>
#> 1 2000 Liberal Man 5357. 0.451
#> 2 2000 Liberal Woman 5318. 0.483
#> 3 2000 Conservative Man 1263. 0.106
#> 4 2000 Conservative Woman 1247. 0.113
#> 5 2000 NDP Man 1045. 0.0880
#> 6 2000 NDP Woman 1146. 0.104
#> 7 2000 Social Credit Man 80.9 0.00681
#> 8 2000 Social Credit Woman 59.4 0.00539
#> 9 2000 Bloc Quebecois Man 1041. 0.0876
#> 10 2000 Bloc Quebecois Woman 942. 0.0855
#> # ℹ 190 more rows
gap_analysis(vote_data, "intention", years = 2000:2022, grouping_var = "gender")
#> The following years are not available: 2012, 2013, 2014, 2016, 2017, 2018
#> # A tibble: 93 × 13
#> # Groups: year [16]
#> year vote cnt_Man cnt_Woman n_Man n_Woman flag estimate1 estimate2
#> <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
#> 1 2000 Liberal 5357. 5318. 11883. 11012. p-va… 0.451 0.483
#> 2 2000 Conservative 1263. 1247. 11883. 11012. p-va… 0.106 0.113
#> 3 2000 NDP 1045. 1146. 11883. 11012. p-va… 0.0880 0.104
#> 4 2000 Social Cred… 80.9 59.4 11883. 11012. p-va… 0.00681 0.00539
#> 5 2000 Bloc Quebec… 1041. 942. 11883. 11012. p-va… 0.0876 0.0855
#> 6 2000 Reform/Alli… 2742. 2008. 11883. 11012. p-va… 0.231 0.182
#> 7 2000 Other 353. 292. 11883. 11012. p-va… 0.0297 0.0265
#> 8 2001 Liberal 1426. 1598. 3315. 3361. p-va… 0.430 0.476
#> 9 2001 Conservative 581. 525. 3315. 3361. p-va… 0.175 0.156
#> 10 2001 NDP 332. 377. 3315. 3361. p-va… 0.100 0.112
#> # ℹ 83 more rows
#> # ℹ 4 more variables: p.value <dbl>, conf.low <dbl>, conf.high <dbl>,
#> # diff <dbl>Note that vote_data predates the cp3 Parquet pipeline
and has a single community_size column rather than the live
data path’s com_100/com_500 split – see
demographic_vars for the live path’s version of each
demographic variable.