Skip to contents

Version française

qesR loads the Quebec Election Studies and related Quebec election surveys into R by study code. Each study code reads the study’s original data file, always the same version, checked before use. The catalog of studies, their documents, their citations and the description of every variable of every study ship with the package.

This page goes from a study code to a weighted estimate. Every chunk on it runs without a network connection, except the last section: it uses the catalog, the shipped metadata and qes_demo, a small synthetic study that ships with qesR. The chunks that would download a real study are shown but not run, and the last section, On the real data, runs the same steps on one real study, which it downloads.

Install

Install qesR from GitHub, and the survey package for the estimates with confidence intervals:

# install.packages("remotes")
remotes::install_github("ThomasGareau/qesR")
install.packages("survey")

Which studies are there?

qes_studies() lists every study, with its year, family, design, target population, licence, DOI and the version of its data that qesR reads.

studies <- qes_studies()
studies[, c("study", "year", "family", "study_design", "licence")]
#>                 study year       family study_design      licence
#> 1             qes2022 2022          qes     pre_post CC BY-NC 4.0
#> 2             qes2018 2018          qes         post      CC0 1.0
#> 3       qes2018_panel 2018 durand_panel        panel      CC0 1.0
#> 4             qes2014 2014          qes         post      CC0 1.0
#> 5             qes2012 2012          qes         post      CC0 1.0
#> 6       qes2012_panel 2012 durand_panel        panel      CC0 1.0
#> 7  qes_crop_2007_2010 2007   crop_polls pooled_polls      CC0 1.0
#> 8             qes2008 2008          qes         post      CC0 1.0
#> 9             qes2007 2007          qes         post      CC0 1.0
#> 10      qes2007_panel 2007 durand_panel        panel      CC0 1.0
#> 11            qes1998 1998   polls_1998        panel      CC0 1.0
#> 12       qes1998_crop 1998   polls_1998        panel      CC0 1.0
#> 13    qes1998_createc 1998   polls_1998        panel      CC0 1.0

Not every study is a Quebec Election Study (family qes): the Durand panels, the CROP polls and the 1998 polls have their own designs and populations. qes_docs() lists the codebooks, questionnaires and reports of each study.

qes_docs("qes2014")[, c("role", "lang", "file_name")]
#>               role lang
#> 1    questionnaire   en
#> 2    questionnaire   fr
#> 3 technical_report   fr
#>                                                                 file_name
#> 1                                       Quebec Election Study 2014 EN.doc
#> 2                                       Quebec Election Study 2014 FR.doc
#> 3 Quebec Election Study 2014 - Technical Report - Post-Election Study.doc

Load a study

get_qes() returns the data. It writes nothing into your workspace, so assign the result yourself. The demonstration study is read from the package; a real study is downloaded once per session (or kept between sessions with options(qesR.cache = "disk")).

demo <- get_qes("qes_demo", quiet = TRUE)
dim(demo)
#> [1] 60 11
head(demo[, c("QSEXE", "Q2", "Q3", "Q19")])
#>   QSEXE Q2 Q3 Q19
#> 1     2  1  3   2
#> 2     2  1  4   2
#> 3     1  1  1   1
#> 4     1  1  2   1
#> 5     1  1  4   2
#> 6     2  1  3   1
qes2014 <- get_qes("qes2014")

Columns keep the codes and labels of the file (haven labelled columns). Codes such as “don’t know” and “refused” are values until you ask qes_missing() to set them to NA; it records what it changed.

demo_clean <- qes_missing(demo, quiet = TRUE)
attr(demo_clean, "qes_missing_log")
#>   variable value missing_type n_set
#> 1     QAGE  9999      refused     0
#> 2       Q2     9      refused     3
#> 3       Q3    99      refused     2
#> 4      Q19     8           dk     3
#> 5      Q19     9      refused     2
#> 6      Q28     8           dk     1
#> 7      Q28     9      refused     0
#> 8      Q32    98           dk     5
#> 9      Q32    99      refused     0

Codebooks, questions and search, offline

The codebook of every study ships with qesR: variable labels, question text in English and French from the deposited questionnaires (for the 2022 study, its bilingual codebook), value labels and missing codes.

cb <- qes_codebook("qes2014")
cb[cb$variable %in% c("Q2", "Q3", "Q19", "POND"), c("variable", "label", "n_value_labels")]
#> <qes_codebook>
#> Variables: 4 
#> Codebook/support files: 0 
#>   variable                                    label n_value_labels
#> 1       Q2 Avez-vous voté à cette élection provi...              3
#> 2       Q3          Pour quel parti avez-vous voté?              8
#> 3      Q19 Si un référendum sur l'indépendance a...              4
#> 4     POND                              Pondération              0
qes_codebook("qes2014", layout = "long", variables = "Q19")
#> <qes_codebook> survey: qes2014
#> DOI: 10.5683/SP3/64F7WR 
#> Data file: Quebec Election Study 2014.sav (Dataverse: Quebec Election Study 2014 (SPSS).tab) 
#> Variables: 1 
#> Rows: 4 
#> Codebook/support files: 3 
#>   variable value                value_label
#> 1      Q19     1                        Oui
#> 2      Q19     2                        Non
#> 3      Q19     8             Je ne sais pas
#> 4      Q19     9 Je préfère ne pas répondre
#>                                      label
#> 1 Si un référendum sur l'indépendance a...
#> 2 Si un référendum sur l'indépendance a...
#> 3 Si un référendum sur l'indépendance a...
#> 4 Si un référendum sur l'indépendance a...
#>                                   question   study missing_type is_declared_na
#> 1 Si un référendum sur l’indépendance a... qes2014         <NA>          FALSE
#> 2 Si un référendum sur l’indépendance a... qes2014         <NA>          FALSE
#> 3 Si un référendum sur l’indépendance a... qes2014           dk          FALSE
#> 4 Si un référendum sur l’indépendance a... qes2014      refused          FALSE

qes_question() gives the exact wording, in either language:

qes_question("qes2014", "Q19", lang = "en")$question
#> [1] "If there were a referendum on independence that asked whether Quebec should be an independent country, would you vote YES or NO?"
qes_question("qes2014", "Q19", lang = "fr")$question
#> [1] "Si un référendum sur l’indépendance avait lieu vous demandant si vous voulez que le Québec devienne un pays indépendant, voteriez-vous OUI ou voteriez-vous NON?"

qes_search() searches every study at once, ignoring case and accents:

hits <- qes_search("souverain|sovereign")
head(hits[!is.na(hits$label), c("study", "variable", "label")])
#>     study            variable                                    label
#> 1 qes2022 cps_impissue_matrix What is the most important issue to y...
#> 2 qes2022             pes_q23 Did you vote in the 1995 referendum o...
#> 3 qes2014                  Q1 Parmi les enjeux suivants, lequel éta...
#> 4 qes2014                 Q16 Avez-vous voté lors du référendum de ...
#> 5 qes2014                 Q50 Certaines personnes croient qu'il est...
#> 6 qes2012               q34bb Of the following issues, which was, f...

The codebook of the 2022 study is CC BY-NC 4.0 (attribution, no commercial use); see vignette("citations", package = "qesR").

A weighted estimate

What share of Quebec adults would have voted YES in a referendum on independence, just after the 2014 election? Question Q19 of qes2014 asks it (its wording is above). A correct estimate states four things, and the steps above give each of them.

  1. The population. The catalog gives each study’s design and target population: qes2014 is a post-election survey that targets the Quebec adult population, nothing wider. Its respondents come from an opt-in online panel, not from a probability sample.
  2. The weight. POND is the weight of qes2014. Its technical report (qes_docs("qes2014")) says it adjusts the sample to the latest census by sex, age, region and language. The estimate describes the adult population only to the extent that this adjustment corrects the panel.
studies[studies$study == "qes2014",
        c("study", "study_design", "target_population_en", "target_population_fr")]
#>     study study_design    target_population_en        target_population_fr
#> 4 qes2014         post Quebec adult population Population adulte du Québec
qes_codebook("qes2014", variables = "POND")[, c("variable", "label")]
#> <qes_codebook>
#> Variables: 1 
#> Codebook/support files: 0 
#>   variable       label
#> 1     POND Pondération
  1. The question and its codes. Q19 codes YES as 1, NO as 2, “don’t know” as 8 and “refused” as 9 (the long codebook above).
  2. The denominator. qes_missing() set codes 8 and 9 to NA. The share below is among respondents who answered YES or NO, and says so.

On the demonstration study, which copies these names and codes:

answered <- demo_clean$Q19 %in% c(1, 2)
yes <- demo_clean$Q19[answered] == 1
weight <- demo_clean$POND[answered]
c(
  respondents = sum(answered),
  unweighted = mean(yes),
  weighted = sum(weight * yes) / sum(weight)
)
#> respondents  unweighted    weighted 
#>  55.0000000   0.4000000   0.4158442

The demonstration data are synthetic, so these numbers describe no population; they show the computation. On the real file the same lines give the share of YES weighted to the Quebec adult population of 2014; the last section of this page computes it, with its confidence interval.

qes2014 <- qes_missing(get_qes("qes2014"))
answered <- qes2014$Q19 %in% c(1, 2)
yes <- qes2014$Q19[answered] == 1
weight <- qes2014$POND[answered]
sum(weight * yes) / sum(weight)

This is a point estimate. qes2014 is a non-probability online panel: its technical report (qes_docs("qes2014")) gives no margin of error, and a design-based standard error, from the survey package for instance, would not have its usual meaning. Compute each estimate within one study: studies differ in design, population and wording, and their weights are on their own scales.

Harmonized data across studies

qes_harmonize() builds one data frame from several studies, one column per harmonized variable (“target”), following rules that ship with qesR. qes_spec() shows which question of each study feeds a target and how comparable it is to the target’s anchor question:

xw <- qes_spec("crosswalk", targets = "sov_indep", lang = "en")
xw[, c("study", "wave", "source_var", "grade", "weight_var")]
#>     study wave        source_var      grade         weight_var
#> 1 qes2012 post               q52  identical               pond
#> 2 qes2014 post               Q19  identical               POND
#> 3 qes2018 post               q26 comparable               pond
#> 4 qes2022  cps cps_qc_referendum comparable cps_weight_general

Not every study has a validated weight: the weights table of the website says which do. Where a study has none, its weight columns are NA, and qes_design() tells you when it leaves rows out. On the demonstration study, which stands in for qes2014:

h <- qes_harmonize("qes_demo", targets = c("sov_indep", "vote_prov_recall"),
                   missing = "reasons", quiet = TRUE, lang = "en")
h[1:4, c("study", "eligible_voter", "sov_indep", "sov_indep__na", "weight_post")]
#>      study eligible_voter sov_indep sov_indep__na weight_post
#> 1 qes_demo           TRUE        No          <NA>   1.2593217
#> 2 qes_demo           TRUE        No          <NA>   0.6685168
#> 3 qes_demo           TRUE       Yes          <NA>   1.3786438
#> 4 qes_demo           TRUE       Yes          <NA>   0.9836078
attr(h, "qes_weight_guide")[, c("target", "study", "weight_column", "weight_var")]
#>             target    study weight_column weight_var
#> 1 vote_prov_recall qes_demo   weight_post       POND
#> 2        sov_indep qes_demo   weight_post       POND

Every missing value has a reason (sov_indep__na). eligible_voter says whether the respondent could vote in the election, and each wave’s recommended weight has mean 1: these questions were asked after the election, so weight_post is their weight, as the weight guide says. qes_design() hands the data to the survey package. droplevels() first removes the answers no respondent of the demonstration study gave, so that they do not show as rows of zeros:

if (requireNamespace("survey", quietly = TRUE)) {
  h$sov_indep <- droplevels(h$sov_indep)
  d <- qes_design(h, weight = "weight_post")
  survey::svymean(~sov_indep, d, na.rm = TRUE)
}
#>                 mean     SE
#> sov_indepYes 0.41584 0.0718
#> sov_indepNo  0.58416 0.0718

The standard error treats the weighted sample as a probability sample; for an opt-in panel such as qes2014, read it as a rough guide only (see ?qes_design).

A pre-election question (vote intention) takes weight_pre instead, and a study’s two waves can have different weights: qes_design() asks you to choose when the targets need different ones. The reference of every target, generated from the specification, is vignette("harmonization-reference", package = "qesR").

For a quicker start across every study, qes_decon() returns one flat data frame with plain column names (vote_choice, sovereignty, education, …), relaxed so that each column holds one concept even where the questions differ; see One file for every study on the website.

Provenance and citations

Data returned by get_qes() records which file it was read from. qes_provenance() returns that record, and qes_cite() cites qesR and the datasets you used (see vignette("citations", package = "qesR")).

qes_provenance(demo)
#> qes_demo: file 0 (qes_demo.sav), synthetic data shipped with qesR. md5
#> e956e315800690cb0894c86ed85c8bea, verified. 60 rows, 11 columns. Retrieved on
#> 2026-10-06 05:13:31 UTC (local_demo). Read with haven::read_sav(user_na =
#> TRUE), haven 2.5.5. Licence: CC0 1.0. qesR catalog 2.4.1.
#> 
#> as.data.frame() gives every column.
qes_cite("qes2014")
#> [1] "Gareau-Paquette, Thomas, 2026, \"qesR: Access Quebec Election Study Datasets\", R package version 0.9.2, https://github.com/ThomasGareau/qesR"                
#> [2] "Bélanger, Éric; Nadeau, Richard, 2023, \"Étude électorale québécoise 2014\", https://doi.org/10.5683/SP3/64F7WR, Borealis, V1, UNF:6:OoiAJ3ShbycsxmWCefqrjw=="

qes_download() saves the original files themselves (data file and documents), checked, in a folder you choose:

dir <- file.path(tempdir(), "qes_originals")
dir.create(dir)
qes_download("qes2014", path = dir, what = c("data", "docs"))

Messages and errors

Messages, warnings and errors can be shown in French with options(qesR.lang = "fr"); the data returned is the same in both languages. Errors have classes, so a script can react to them without reading their text:

tryCatch(
  get_qes("QES 2014"),
  qesR_error_unknown_study = function(e) e$suggestions
)
#> [1] "qes2014"

On the real data

The same estimate on the real qes2014, from harmonization to a share with its 95% confidence interval. This section downloads one study (about 0.4 MB) the first time it runs; options(qesR.cache = "disk") keeps it on disk for later sessions. The interval treats the weighted panel as a probability sample, so for an opt-in panel such as qes2014 read it as a rough guide.

library(survey)
h14 <- qes_harmonize("qes2014", targets = "sov_indep", quiet = TRUE)
d14 <- qes_design(h14, weight = "weight_post")
d14 <- subset(d14, sov_indep %in% c("Yes", "No"))
svyciprop(~I(sov_indep == "Yes"), d14, method = "logit")
#>                              2.5% 97.5%
#> I(sov_indep == "Yes") 0.348 0.318 0.379

qes_harmonize() labels the answers in English by default; lang = "fr" gives them in French.

Code written for an earlier version

See Upgrading from qesR 0.4.4, vignette("migrating-0.7", package = "qesR").