27  Importing and Exporting JASP, jamovi, and SPSS Viewer Files

Important

This chapter needs the data_check branch of metacheck, since import_jasp(), import_omv(), import_spv(), and the three export_*_html() functions are recent, not-yet-released additions. These functions will eventually make it to main.

27.1 Overview

Metacheck is an R based tool, but it would be useful to load data, code, and output files from common statistical software packages. This would allow the reproducibility_check module to take the output, and check it against the values reported in the manuscript. In psychology, SPSS, JASP, and Jamovi are commonly used. JASP and Jamovi have made the extremely sensible design choice that a .jasp or .omv file contain the data, code, and output. This means that sharing those files guarantees complete reproducibility of the analysis by design.

Three statistics packages each save their own binary archive format. There are, as far as we are aware, no existing functions in R that can read in a .spv (SPSS), .jasp (JASP), or .omv (Jamovi) file. Therefore we have made functions that can read in these files, and export the output to a html file. This makes it easier for people who want to re-use data to access the files, which improves the interoperability of these file-types.

Each format is a single archive that bundles the data, its variable metadata (measurement level, value labels), and the analyses that were run against it — which is exactly why reproducibility_check treats a self-contained .jasp/.omv/.spv file as reproducible “for free”: no code needs to be executed, because the results are already stored inside the file itself.

There is exactly one import function per format:

  • import_jasp(path)
  • import_omv(path)
  • import_spv(path)

and, going the other way, exactly one export function per format that turns the archive into a single, readable, portable HTML file:

  • export_jasp_html(path, out = NULL)
  • export_omv_html(path, out = NULL)
  • export_spv_html(path, out = NULL)

This chapter demonstrates all six against small real example files (example.jasp, example.omv, example.spv, example-chart.spv), committed alongside this chapter so the examples below execute for real when the book is rendered.


27.2 Part 1: Importing data — import_jasp() and import_omv()

27.2.1 Why JASP and jamovi are read like SPSS files

A .jasp or .omv archive is a ZIP file. Inside, alongside the raw data, each variable carries the same kind of metadata an SPSS .sav file carries: a measurement level (nominal, ordinal, continuous) and, for categorical variables, value labels mapping a numeric code (1, 2) to a meaningful label ("Control", "Treatment"). Metacheck reads that metadata using the same haven-style label / labels attributes that haven::read_sav() attaches — so a .jasp or .omv file can serve as its own codebook, exactly like a .sav file, with no special-casing anywhere downstream (see codebook_check).

27.2.2 Reading a JASP file

jasp <- import_jasp("example.jasp")

# the dataset itself
dim(jasp$data)
#> [1] 32 40
head(jasp$data)
#>   Age Gender Information_page_time Exclude_for_timing Heard_of_procedure
#> 1  20      2                 9.133                  1                  1
#> 2  21      2                12.832                  0                  1
#> 3  20      2                32.733                  0                  1
#> 4  19      2                11.299                  0                  1
#> 5  20      2                10.265                  0                  1
#> 6  20      2                13.510                  0                  1
#>   Previously_participated_in_RHI S1_sync S1_async S2_sync S2_async S3_sync
#> 1                              1       2       -1       2        1       2
#> 2                              0       2        1       1       -1       2
#> 3                              1       2        1       3        1       2
#> 4                              1       2       -2      -1        1      -1
#> 5                              1       2       -1       3       -1       3
#> 6                              1       2        1       1        0      -1
#>   S3_async C1_sync C1_async C2_sync C2_async C3_sync C3_async C4_sync C4_async
#> 1        0       2       -1       2        1       2        1      -1       -1
#> 2        1       1        0       0        0       0       -2       0       -1
#> 3        0       1       -1      -2       -3       3        1      -3       -3
#> 4        2      -2        1      -2        0       0        0      -1        1
#> 5       -1       1        0       2       -1       0        0      -1       -1
#> 6       -1      -1       -1      -2       -2      -2       -2      -3       -3
#>   C5_sync C5_async C6_sync C6_async id S1_time S2_time S3_time C1_time C2_time
#> 1       2       -1       2       -1  1  12.210  14.855  19.207  10.785  11.737
#> 2      -1       -1      -2       -2  2  20.853   8.504   8.704   7.362  20.677
#> 3       1       -1       1       -1  3  13.191  10.085 184.026  17.548  11.260
#> 4      -2        1      -2        1  4   4.740   4.537   6.737   4.257   5.456
#> 5       0       -1       1       -2  5  17.294   7.044   8.924   8.271   6.195
#> 6      -2       -2      -2       -2  6   8.008   5.541   7.304   5.076   5.399
#>   C3_time C4_time C5_time C6_time Illusion_score_sync Illusion_score_async
#> 1   7.722  11.340   8.530   8.530           2.0000000            0.0000000
#> 2   8.478   7.235   7.789   7.788           1.6666667            0.3333333
#> 3 102.887  30.484  14.253  14.252           2.3333333            0.6666667
#> 4   7.335   3.863   3.265   3.265           0.0000000            0.3333333
#> 5   4.683   6.951   8.290   8.289           2.6666667           -1.0000000
#> 6   4.922   4.896   4.726   4.726           0.6666667            0.0000000
#>   control_score_sync control_score_async Expect_question_time_mean
#> 1          1.5000000          -0.3333333                 11.657333
#> 2         -0.3333333          -1.0000000                 10.821111
#> 3          0.1666667          -1.3333333                 44.220667
#> 4         -1.5000000           0.6666667                  4.828333
#> 5          0.5000000          -0.8333333                  8.437889
#> 6         -2.0000000          -2.0000000                  5.622000
#>   Free_response
#> 1            19
#> 2            13
#> 3            21
#> 4            29
#> 5             6
#> 6            27

import_jasp() returns a list with four elements:

Element Contents
$data the dataset as a data.frame; labelled columns carry label / labels attributes
$columns a data.frame of name and type (JASP’s own measurement-level classification)
$analyses the parsed analyses.json — JASP’s own record of every analysis run
$format "binary" (JASP ≤ ~0.16) or "sqlite" (JASP ≥ ~0.17) — both are handled transparently
$data_file_path the original file JASP imported the data from, if recorded

Value labels are attached exactly like haven::read_sav() attaches them:

labelled <- vapply(jasp$data, function(col) !is.null(attr(col, "labels")), logical(1))
names(jasp$data)[labelled]
#>  [1] "Age"                            "Gender"                        
#>  [3] "Exclude_for_timing"             "Heard_of_procedure"            
#>  [5] "Previously_participated_in_RHI" "S1_sync"                       
#>  [7] "S1_async"                       "S2_sync"                       
#>  [9] "S2_async"                       "S3_sync"                       
#> [11] "S3_async"                       "C1_sync"                       
#> [13] "C1_async"                       "C2_sync"                       
#> [15] "C2_async"                       "C3_sync"                       
#> [17] "C3_async"                       "C4_sync"                       
#> [19] "C4_async"                       "C5_sync"                       
#> [21] "C5_async"                       "C6_sync"                       
#> [23] "C6_async"                       "Free_response"

# one example
attr(jasp$data[[names(jasp$data)[labelled][1]]], "labels")
#> 18 19 20 21 23 25 45 
#> 18 19 20 21 23 25 45

27.2.3 Reading a jamovi file

import_omv() is the jamovi counterpart, returning the same shape:

omv <- import_omv("example.omv")

dim(omv$data)
#> [1] 218   9
omv$columns
#>                         name       type
#> 1                 Experiment    Nominal
#> 2                Participant Continuous
#> 3         Manipulation check Continuous
#> 4                    H1 test Continuous
#> 5                    H2 test Continuous
#> 6 H3 confirmatory confidence Continuous
#> 7      H3 dissent confidence Continuous
#> 8                    H4 test Continuous
#> 9           Guess hypothesis         ID

The one difference is $analyses: jamovi does not store a structured analyses.json the way JASP does, so import_omv() recovers each analysis as a readable one-line summary, including the reproducible R syntax jamovi embeds internally when it is recoverable:

omv$analyses
#> [1] "1. 05 ttestOneS  |  jmv::ttestOneS( data = data, vars = H4 test, hypothesis = \"gt\", desc = TRUE)"

27.2.4 Using the data like any other data source

Because both readers attach the same haven-style attributes, .jasp and .omv files flow through the rest of metacheck exactly like a .sav file — for example, data_check and codebook_check both read them with no special code path:

mo <- module_run(paper, "data_check", local_path = "path/to/study_folder")

If that folder contains a .jasp or .omv file, its embedded labels are harvested as if they were a codebook, and its data is read as if it were a .sav.


27.3 Part 2: Importing SPSS Viewer output — import_spv()

27.3.1 What a .spv file is, and why it is different

A .spv file is not a data file at all — it is IBM SPSS’s rendered output window, saved to disk. It contains the result tables SPSS produced (a T-Test table, a Reliability Statistics table, an ICC table, …) together with the exact syntax that produced them, and, when the output included one, any charts the analysis generated (a Q-Q plot, a scatterplot with a fitted curve, a box plot, …). Unlike .jasp/.omv, .spv has no rendered HTML view baked into the archive — everything has to be decoded from SPSS’s own internal representation.

.spv has no public specification. Metacheck’s table reader is ported from GNU PSPP’s GPL-licensed decoder — the only known public implementation of this format — and handles both the older (“legacy”) and modern (SPSS 21+, “light-binary”) table encodings transparently. Charts have no PSPP implementation to port from at all (PSPP itself does not render .spv charts); metacheck’s chart decoder was built entirely by inspecting real .spv files, described in Part 3 below.

27.3.2 Reading an SPSS Viewer file

tables <- import_spv("example.spv")

length(tables)
#> [1] 8

# what analyses were recovered
unique(vapply(tables, function(tb) tb$analysis %||% NA_character_, character(1)))
#> [1] "Reliability"

import_spv() returns a list of result tables — the same shape read_stat_tables() already returns for .jasp and .omv, so all three formats can be processed identically downstream. Each table is a list with:

Element Contents
analysis the SPSS command that produced it (e.g. "RELIABILITY")
title the table’s own caption
data a tidy data.frame: one row per cell, one column per row/column dimension, plus value (or, for a chart entry, its plotted points — see Part 3)
syntax the exact SPSS syntax recovered from the archive, when recoverable
table_index the table’s 1-based position in the original output document
is_chart TRUE for a chart entry, FALSE for a result table
tb <- tables[[1]]
tb$analysis
#> [1] "Reliability"
tb$title
#> [1] "Notes"
tb$data
#>                                          Contents
#> 1                                  Output Created
#> 2                                        Comments
#> 3                                    Input / Data
#> 4                          Input / Active Dataset
#> 5                                  Input / Filter
#> 6                                  Input / Weight
#> 7                              Input / Split File
#> 8          Input / N of Rows in Working Data File
#> 9  Missing Value Handling / Definition of Missing
#> 10            Missing Value Handling / Cases Used
#> 11                                         Syntax
#> 12                     Resources / Processor Time
#> 13                       Resources / Elapsed Time
#> 14                           Input / Matrix Input
#>                                                                                                                                    value
#> 1                                                                                                                        13689606611.578
#> 2                                                                                                                                       
#> 3  D:\\stack\\Content\\PhD\\Projects\\OpenData_ReportingErrors\\Study2_PLOSvsFrontiers\\Data\\CodedData\\160518DoubleCodedDataCoders.csv
#> 4                                                                                                                               DataSet2
#> 5                                                                                                                                 <none>
#> 6                                                                                                                                 <none>
#> 7                                                                                                                                 <none>
#> 8                                                                                                                                    399
#> 9                                                                                                                     MISSING=EXCLUDE...
#> 10                                                                                                                  Statistics are based
#> 11                                                                                                                            [:^1\\n:]1
#> 12                                                                                                                                     0
#> 13                                                                                                                                 0.043
#> 14

The recovered syntax is worth looking at directly — this is the exact command SPSS ran, extracted from the output file alone (useful when a researcher shared .spv output but not their .sps syntax file):

tb$syntax
#> [1] "NEW FILE.\nDATASET NAME DataSet1 WINDOW=FRONT.\n\nGET DATA  /TYPE=TXT\n  /FILE=\"D:\\stack\\Content\\PhD\\Projects\\OpenData_ReportingErrors\\Study2_PLOSvsFrontiers\\Data\\CodedData\\160518DoubleCodedDataCoders.csv\"\n  /ENCODING='UTF8'\n  /DELCASE=LINE\n  /DELIMITERS=\" ;\"\n  /QUALIFIER='\"'\n  /ARRANGEMENT=DELIMITED\n  /FIRSTCASE=2\n  /IMPORTCASE=ALL\n  /VARIABLES=\n  article A18\n  coder1 F1.0\n  coder1IN A5\n  coder2 F1.0\n  coder2IN A5\n  DataPromised1 F1.0\n  DataPromised2 A2\n  DataAvailable1 F1.0\n  DataAvailable2 A2\n  Remarks1 A234\n  Remarks2 A221.\nCACHE.\nEXECUTE.\nDATASET NAME DataSet2 WINDOW=FRONT.\nDATASET ACTIVATE DataSet2.\nRELIABILITY\n  /VARIABLES=DataAvailable1 DataAvailable2\n  /SCALE('ALL VARIABLES') ALL\n  /MODEL=ALPHA\n  /ICC=MODEL(RANDOM) TYPE(ABSOLUTE) CIN=95 TESTVAL=0."

27.3.3 Result tables feed the same pipeline as JASP and jamovi

Because import_spv()’s tables share JASP/jamovi’s shape, the same downstream functions apply without modification:

long <- stat_results_long(tables, paper_id = "example", source_file = "example.spv")
head(long[, c("analysis", "row_label", "statistic", "stato_label", "value")])

27.4 Part 3: Charts inside .spv files

Some .spv files also contain charts — a Q-Q plot from an Explore normality check, a scatterplot with a fitted trend line from Curve Fit, a box plot from a Nonparametric Tests comparison. import_spv() recovers these too, as extra entries in the same list of tables, distinguished by is_chart = TRUE:

chart_tables <- import_spv("example-chart.spv")
length(chart_tables)
#> [1] 2

is_chart <- vapply(chart_tables, function(tb) isTRUE(tb$is_chart), logical(1))
which(is_chart)
#> [1] 2

A chart entry’s $data holds the actual plotted points rather than a result table:

chart <- chart_tables[[which(is_chart)]]
chart$analysis
#> [1] "Graph"
chart$title
#> [1] "The Stochastic Relationship Between the Total Population of the County and the Number of Health Development Offices in Hungary as of 2022"
head(chart$data)
#>         x y
#> 1 1671004 7
#> 2 1328790 6
#> 3  419565 5
#> 4  301492 0
#> 5  338432 6
#> 6  471309 4
spv_chart_type $data columns What it represents
"point" x, y A scatterplot’s plotted points (e.g. Curve Fit, a Q-Q plot)
"boxplot" category, value A box plot’s raw per-case values, grouped by category

A "point" chart also carries any fitted trend line SPSS itself already computed — the exact formula, not something metacheck estimates:

attr(chart$data, "spv_chart_fits")
#> [[1]]
#> [[1]]$name
#> [1] NA
#> 
#> [[1]]$expr
#> [1] "4.03+2.86E-6*x"
#> 
#> [[1]]$fn
#> function (x) 
#> 4.03 + 2.86e-06 * x
#> <environment: 0x000001dd6ae292a0>

stat_results_long() and stat_output_json() skip chart entries automatically (a chart’s x/y or category/value columns are plotted points, not statistics to extract), so passing import_spv()’s output straight through the usual pipeline is always safe, chart-bearing file or not.

Note

.spv’s chart format has no public specification and no PSPP implementation to port from — it was reverse-engineered from real files found on Zenodo and Figshare. Two chart mark types are currently supported (<point> and <schema>/box plot, above); others (bar charts, line charts) are recognised but not yet decoded, and are silently skipped rather than causing an error. Across a sample of real .spv files with charts, roughly 60% of individual charts decode successfully with the two supported types.


27.5 Part 4: Exporting readable HTML — export_jasp_html(), export_omv_html(), export_spv_html()

Sometimes you don’t want the data out of a .jasp/.omv/.spv file — you want to look at the output, the way the original software would show it, without having JASP, jamovi, or SPSS installed. Each format has its own export function for exactly this.

27.5.1 JASP and jamovi: re-exporting the software’s own rendered output

A .jasp or .omv archive already contains a fully rendered index.html — the same output view JASP or jamovi itself displays, including any plots, saved as part of the archive. export_jasp_html() and export_omv_html() extract that index.html as-is and inline every plot it references as a base64 image, so the result is a single, portable .html file with nothing else to keep alongside it — you can email it, or open it years later, with no dependency on the original archive.

export_jasp_html("example.jasp")

By default, the HTML file is written next to the source archive with the same name (example.jaspexample.html). Pass out = to choose a different destination:

export_jasp_html("example.jasp", out = "example-jasp-output.html")
export_omv_html("example.omv", out = "example-omv-output.html")

The result looks exactly like JASP’s or jamovi’s own output window — because it is that output window, just re-packaged as one file.

27.5.2 SPSS Viewer: building a readable view from the extracted tables and charts

Unlike .jasp/.omv, a .spv archive has no rendered view of its own to re-export. export_spv_html() instead builds a page from what import_spv() already decodes: one heading per analysis, its recovered syntax shown underneath, one HTML table per result table, and — for a chart entry — an actual plot, rendered from the chart’s own decoded points and embedded as a base64 image, the same way export_jasp_html() inlines JASP’s plots.

export_spv_html("example.spv", out = "example-spv-output.html")

example.spv has no charts, so this particular export is tables-only. example-chart.spv — the file from Part 3 — does have one, and its export includes the rendered scatterplot:

export_spv_html("example-chart.spv", out = "example-chart-output.html")

Not every chart in a .spv file can be rendered this way yet — see the note in Part 3 on which mark types are currently supported. A chart that cannot be decoded is skipped, with the rest of the file’s tables (and any other decodable charts) still exported normally.


27.6 Summary

Task JASP jamovi SPSS Viewer
Import data + analyses import_jasp(path) import_omv(path) import_spv(path)
Export readable HTML export_jasp_html(path, out) export_omv_html(path, out) export_spv_html(path, out)
Rendered view in the archive? Yes (re-exported, plots inlined) Yes (re-exported, plots inlined) No (tables and charts both built fresh from decoded data — see Part 3)

All three importers return data in the shape the rest of metacheck already expects — a labelled data.frame for import_jasp()/import_omv(), and read_stat_tables()’s table-list shape for import_spv() — so data_check, codebook_check, and reproducibility_check all consume these formats automatically, with no extra steps.