Skip to content

About

Free, open-source desktop app for mixed methods research: qualitative coding and quantitative analysis in one local-first project. Code interviews, prepare and analyze survey data, and integrate both on a canvas.

Topics

Resources

Code of conduct

Contributing

Security policy

Accessibility

Stars

0 stars

Watchers

0 watching

Forks

Mixed Measures

A local-first desktop workspace for mixed-methods research — qualitative and quantitative data in one project, with shared participants, codes, and memos.

License: Apache 2.0 CI Platform: Windows | macOS | Linux Python 3.12+ Node 24+

Mixed Measures — project overview

Qualitative coding workbench showing a coded focus-group transcript Quantitative dataset grid with typed columns Observation workbench: a recording coded as clips on its timeline Integration canvas with themes written up as prose
Qualitative coding Quantitative data Observations Integration canvas

The problem

Researchers who work across both interviews and surveys end up living in two disconnected tools: a qualitative coding app for transcripts and documents, and a statistics package for the numbers. The integration — the part that actually makes it mixed methods — happens by hand, in a separate document, with no shared participants, no shared codebook, and no through-line from a survey item to the quote that explains it.

Mixed Measures keeps both kinds of data in a single project. The same participant can be a survey respondent and an interview speaker. A code applies to a transcript segment and to an open-ended survey response. And an integration workspace (the Canvas) lets you write up findings with live excerpts, memos, and analysis results embedded inline.

It is built for independent consultants, program evaluators, and applied researchers who need rigor without an enterprise license — and who want their data to stay on their own machine.

Install

Recommended: download the latest signed installer from the Releases page and run it — no setup required:

Platform File
Windows 10 or 11 (x64) .exe installer
macOS 14 (Sonoma) or later — Apple Silicon only (M1 or later) .dmg
Linux (x86_64, glibc 2.38 or newer — e.g. Ubuntu 24.04+) .AppImage — mark it executable, then run it

Intel Macs are not supported. Not sure which you have? Apple menu → About This Mac; the Chip line reads "Apple M1" (or a later Apple chip) on a supported machine, and the macOS line must read 14 or higher.

Prefer to build it yourself? See Running from source.

First launch — a security prompt is expected. The installers are signed (and notarized on macOS), so the verified publisher is George Chavez. Because Mixed Measures is a new, independent app, your operating system may still show a first-run warning until the app builds up download reputation — this is normal and does not mean anything is wrong:

  • Windows — if you see "Windows protected your PC" (SmartScreen), click More info → Run anyway, confirming the publisher reads George Chavez. (A code signature verifies the publisher and that the file hasn't been tampered with; it does not by itself suppress SmartScreen for a newly released app — that reputation is earned as more people download it.)
  • macOS — the app is notarized, so it should open normally. If it doesn't, right-click the app → Open.

What it does

Bring data in

  • Datasets — import survey/quantitative data from CSV, Excel (.xlsx), or SPSS (.sav), with automatic column-type detection (Likert/scale, numeric, percentage, binary, categorical), scale-pattern recognition, and N/A / refusal-label handling. SPSS files bring their own value labels, scale order, and user-missing codes, so an ordinal variable arrives with the order and codes it was recorded with rather than a guess. Append additional rows from another file — or start from a blank table and type records in by hand, for the case attributes and small matrices that never existed as a file.
  • Documents — import .docx, .pdf, and .txt files; they're auto-segmented (with page numbers and headings) for coding.
  • Conversations — import transcripts as CSV (speaker- and timestamp-aware, e.g. exports from common transcription tools) or as VTT/SRT subtitle files (Zoom and Teams transcript exports import directly), with an optional recording attached for synchronized playback while you code.
  • Observations — import a recording on its own, with no transcript, and code what happens on the timeline itself. Start from an empty timeline, slice it into fixed intervals, or seed the first clips from a cue file.
  • Codings — bring in coding done elsewhere from a CSV (unit_id, coder, code, optionally a rating), applied to passages already in the project: transcript turns, document paragraphs, observation clips or open-text responses (Analysis → Import codings…). Every coder name in the file needs an explicit answer, and before anything is applied the page shows which rows will be imported and why any others will not. The Coded Segments export imports back as it is, ratings included. Labels a model produced elsewhere arrive the same way, as a machine coder that records which model, how it was reached, its settings and its prompt — attributed and filterable, and kept out of every agreement figure.

Describe and prepare variables

Every dataset has two views. Data is the grid of records; Variables is where a variable is described.

  • A variable dictionary — name and label, type, and the value labels that say what each stored code means (1 = "Strongly disagree"), so exports and analysis read in words rather than numbers.
  • Declare which values count as missing — per variable, or one vocabulary across many variables at once, which is how real survey exports are shaped (.d, .n, .i for "Don't know", "Refused", "Inapplicable"). Declared-missing values are excluded from statistics and blank in exports, and the data dictionary records what each one meant.
  • Recode rules — collapse categories, map scale points, reverse-score an item, or band a continuous variable into groups (18–24, 25–34, …), with the rule saved and named rather than applied and forgotten. Each saved rule says whether it is currently in effect, and applying one to the variable it sits on is a deliberate step, because it rewrites stored numbers and cannot be undone.
  • Or recode into a new variable and leave the original untouched. The new variable records which variable and which rule produced it.
  • Computed variables via a safe expression language ([Post] - [Pre], IF(...), MEAN(...), COALESCE(...)), re-derived when their sources change.

Work with recordings (audio & video)

Two ways to work with a recording, and the choice is about what you are coding: what was said → a conversation (a transcript, with the recording attached for synchronized playback); what happened → an observation (the recording coded directly on its own timeline, no transcript involved). Formats are the same either way: audio .mp3, .m4a, .wav; video .mp4, .mov, .webm. One recording can serve both — "also code this as an observation" copies the file across, leaving the original and its coding untouched.

With a transcript — conversations

  • Attach audio (.mp3, .m4a, .wav) or video (.mp4, .mov, .webm) to any conversation. Video docks above the transcript in a resizable pane (small/medium/large, a temporary theater mode, and a floating mini-player) — the transcript stays primary: click a row to seek, and the playhead follows the active segment as it plays. Playback speed runs 0.5×–2× with pitch preserved.
  • Zoom: cloud recordings download as an MP4 plus a speaker-labeled .vtt transcript — import the .vtt as a conversation, then attach the MP4.
  • Need a transcript and don't have one? (Zoom local recordings don't produce one.) Free offline transcription tools such as aTrain export .srt files you can import directly; speakers can be assigned to segments after import. Recordings never leave your machine either way. (If you don't need one at all, code the recording as an observation instead — below.)
  • Codec note: the player decodes H.264/AAC MP4, MOV, and WebM (VP8/VP9). HEVC video (the iPhone camera default) uploads but won't play in-app — re-export it as H.264 (most tools call this "MP4 (H.264)").

Without a transcript — observations

  • Code a recording of an event — a classroom, a clinic visit, a home visit, a usability session — by marking clips on its timeline. The recording is the spine; there is no transcript to code against, and none is required.
  • Start the timeline empty (mark clips yourself as you watch), cut it into fixed intervals for interval-style coding, or seed labelled clips from a cue file (.vtt/.srt chapters or subtitles). The wizard previews the cut — clip count, the first clips, and any warnings — before anything is written.
  • Mark and adjust clips from the keyboard: I/O for in/out points, J-K-L transport with frame stepping, 0.1 s boundary nudges (1 s with Shift), typed timecodes, and split/merge by time. A follow mode keeps the view on the playhead as it runs.
  • Clips carry codes, notes, and time-range quotes, and reach the Canvas, search, and the qualitative analysis surfaces like any other source.
  • Coverage shows what share of the timeline you have marked, with a jump to the next unmarked gap.
  • Timed analytics per code: duration, frequency, rate per minute, share of session airtime, bout length, and a stacked codeline of the whole session. Overlapping clips mean per-code airtimes don't sum to covered time — the table says so rather than quietly implying otherwise.
  • Reliability works either way you cut. Leave the clip set open and each coder marks their own boundaries — agreement is then a unitizing problem (Krippendorff's αU at a 100 ms resolution, plus time-binned kappa). Freeze the clip set once the team agrees the cuts, and every coder codes the same clips — which brings the ordinary kappa, side-by-side reconciliation and consensus layer to video unchanged. The bin size is a visible control shown with the number it produced (a wider bin absorbs timing differences and reads as more agreement, so it is part of the result), and per-code prevalence sits beside every kappa — sparse clips on a long recording make agreement look near-perfect while kappa collapses.

Code and analyze qualitatively

  • Four coding surfaces — for conversations, documents, open-ended text columns in datasets, and observations (a recording's timeline) — sharing one keyboard-driven coding layer.
  • A structured codebook (codes, categories, universal codes), coded-segment tracking, excerpts and a Quote Board, memos, notes, and a quick-capture Scratchpad.
  • Rating scales on codes — a code can declare a scale (minimum, maximum, step, anchor labels) and a coder rates each application on it: not just is this present but how much. Unrated is a real, distinct state, never a zero. Ratings carry into the reliability figures, the consensus layer and the exports, and can be recorded on every coding surface — conversation and document transcripts, observation clips, and open-text responses. A second-pass Ratings sweep walks the passages you have coded with a scaled code but not yet rated, one at a time with that code's scale and anchors on screen, so a scale declared after the coding was done can be caught up — and so a run of ratings is given against the same instrument, which is what makes them comparable.
  • Code sets — a group of codes a passage takes exactly one of (a stance that is positive, negative, neutral or ambiguous), coded with a single-choice control and reported on the Reliability tab as one agreement figure per set, with a breakdown of which values coders confuse. You state whether every passage must take a value, because the same coding gives different figures under the two readings.
  • Participants and speakers form a shared cross-source identity spine, so a person links across their survey record, their interview, and the documents that are about them — a report, a workplan, a case file. Datasets with an identifier column ("Participant ID", "Respondent", …) link their rows to participants automatically at import, append, or retroactively; a document is linked by naming its subject, and one person can have any number of them.
  • Qualitative analysis: code frequencies, co-occurrence, a thematic saturation curve, group comparisons of code frequency, and a codebook treemap overview.

Analyze quantitatively

  • Descriptives (means, SDs, frequencies, proportions), with a margin of error on frequency distributions.
  • Distributions: histograms for continuous variables, box plots for group comparisons, and Q–Q plots — each stating the convention it used, so the figure can be reproduced rather than just read.
  • Comparisons: Welch's t-test, one-way ANOVA with Tukey HSD post-hoc, and non-parametric Mann-Whitney U / Kruskal-Wallis H, with effect sizes (Cohen's d, η², ω², ε²). Assumption checks report whether you need the non-parametric test, rather than leaving you to interpret them.
  • Correlation matrices (Pearson / Spearman) with scatter matrix and trendlines.
  • Cross-tabulation with chi-square and Cramér's V, which says when its approximation is unreliable rather than printing a p-value you should not read.
  • Reliability: Cronbach's alpha with per-item diagnostics (alpha if the item were dropped, item-total correlation) and split-half (Spearman-Brown corrected).
  • Missing-data diagnostics: missingness summary, pattern view, and Little's MCAR test, which states which estimates produced it.
  • Crosswalk — harmonize variables that were measured differently across datasets into equivalence groups and analysis domains, then compute scale scores across instruments. This is the workspace's strongest differentiator for multi-instrument survey work.

Integrate

  • A participant table turns code ratings into variables. Where a code carries a rating scale, the tool maintains a table whose rows are the project's participants and whose columns are each person's average rating for a rated code, alongside the number of passages it rests on — because an average over one passage and over eight are the same number and not the same evidence. Those columns are ordinary variables, so they compare, chart, cross-tabulate and export like any survey item. The rows are the tool's and the columns are yours: you can add your own variables beside the scores and type in case attributes, but a row follows the participant list rather than being edited away. Each refresh records when it ran, and says who was coded but not yet rated.
  • The Canvas is a theme-based integration workspace with Writing and Spatial modes, rich-text prose (Tiptap), inline embeds of excerpts / materials / memos, typed relationships between themes, versioned snapshots, and a Convergence Matrix view for triangulating findings.

Export and reproduce

  • CSV and Excel (.xlsx) of data and results.
  • A runnable R script (.R) that reproduces, in R, the statistics in your saved analyses — t-test, ANOVA + Tukey, correlations, chi²/Cramér's V, Mann-Whitney, Kruskal-Wallis, Cronbach's alpha, split-half, and descriptives (with the margin of error on frequencies) — and the agreement between coders on codes, ratings and code sets (Krippendorff's alpha; Cohen's kappa and percent agreement when there are two coders). Each is verified by a round-trip test that R's numbers match the app's. Not in the script: effect sizes, assumption checks, Fisher's exact test, the missing-data diagnostics (Little's MCAR test among them), trend lines, box plots and Q–Q plots, confidence intervals on means and on agreement, kappa and percent agreement with three or more coders, the agreement measures for recordings, the Model comparison tab, and the qualitative analysis figures. Unsaved analyses are not exported.
  • Canvas export to Word (.docx), HTML, and PDF, with charts embedded as images.
  • REFI-QDA codebook (.qdc) — export and import your codebook in the open cross-tool standard, so codes, hierarchy, descriptions and colours move between Mixed Measures and other QDA tools that read the REFI-QDA codebook format.
  • .mmproject — a complete, database-agnostic project archive for moving a whole project between machines or instances. A file in a newer format than the version opening it can read is refused with a message saying to update, rather than half-read; a file from a newer version that writes the same format imports, and anything that version added may not come across — so colleagues exchanging project files should be on the same version.

What it is not

Being honest about scope:

  • Not real-time collaborative. There is no cloud workspace, no accounts, and no live co-editing. Team coding works asynchronously instead: colleagues code separate copies of a project and merge them back together, with per-coder attribution, blind coding, intercoder reliability (Cohen's kappa, Krippendorff's alpha, percent agreement — each with a 95% confidence interval, and a stated reason where one cannot be computed), agreement on code ratings and on code sets as well as on the codes themselves, side-by-side reconciliation, and a derived consensus layer. A separate Model comparison tab, there for a researcher coding alone as well, sets a machine coder's labels beside each person's, per code — a description of the model's behaviour, not inter-rater reliability. Several coders can also take turns on one computer under named identities. (Separate researchers with unrelated projects on a shared computer should still use separate operating-system accounts.)
  • Not cloud-based. Everything runs locally against a local database. Moving a project between machines is a manual file transfer (.mmproject); in the desktop app a backup opens only in the installation that made it.
  • No generative AI, and nothing leaves your computer. Mixed Measures 1.x sends your data nowhere and contains no generative AI — it doesn't analyze your data, write your findings, or upload anything. It can import labels a model produced elsewhere (see Codings, above) — it records where they came from and keeps them out of every agreement figure, but it never runs a model. Every result comes from a conventional, documented method you can inspect and check, computed locally and deterministically. The exported R script reproduces the statistics listed under Export and reproduce above, each checked against R by an automated test, and a statistic joins the script only with such a test. A future 2.x will introduce generative AI — local-first, off by default, and separable from the build. (AI coding tools did assist in building Mixed Measures — an honest note about development, distinct from what the product does.)
  • Withdrawal is supported, but it cannot read — and deleting a participant is still not a withdrawal. Two things exist. A withdrawal report answers "what traces back to this person?" — counts and locations across transcripts, datasets, documents about them, excerpts, notes and memos, never the text itself. And a withdrawal action removes the identity, deletes what is unambiguously theirs (their dataset rows and responses), renames their speaker to a numbered token, and blanks their conversation turns rather than deleting them — a turn removed outright damages the records of participants who did not withdraw, and moves every coverage and reliability figure in the project. A document about them is a third case: it is unlinked and reported, never deleted, because "about this person" is true both of a workplan they wrote and of a policy document that merely names them, and only someone who knows the project can tell those apart. Quotes pointing into blanked text are removed with it; the researcher's own codes, notes and memos are kept and reported for review, because deciding whether a memo quotes the person is reading, not matching. That is the limit that matters: it cannot find the person's name inside other people's turns, or inside free-text answers, notes or memos. Those need someone who knows the project. A full backup is taken first and there is no per-participant undo — restoring reverses everything done since. And deleting a participant record first still makes a withdrawal harder, because it destroys the link used to find the rest. Plan withdrawal handling before you collect. (This describes what the software does; a compliance reviewer should own the conclusion for your study.)
  • English-only interface, and word counts assume spaces. There is no translation layer, and the bundled fonts cover Latin and Latin-Extended only, so other scripts fall back to system fonts and render outside the designed type scale. Word counts are whitespace-delimited: languages written without spaces between words — Chinese, Japanese, Thai — count as one word per segment, which means coding density, per-source volume and the saturation denominator are counting segments rather than words for those corpora. Everything else (coding, retrieval, statistics, export) is script-agnostic and stores text as UTF-8, so a non-Latin project works — you simply should not read the word-based numbers as word counts.
  • Not a full statistical-modeling suite. Regression and factor analysis are not currently included; for analysis beyond the built-in descriptives and comparisons, export the R script and continue there.
  • REFI-QDA support is partial — codebooks yes, whole projects not yet. The standard has two halves. Mixed Measures reads and writes the codebook half (.qdc), so your codes travel to and from tools that support it. It does not yet read or write .qdpx, the full project package that carries sources, coded selections and memos — so a complete project cannot currently move to NVivo or MAXQDA, or be deposited with an archive that requires QDPX. .qdpx export is the next interoperability build; .mmproject is a documented, open archive in the meantime, but it has one implementation and that is the honest limitation.

Privacy & your data

  • Fully local. Your research data never leaves the machine — no telemetry, no analytics, no external CDNs (fonts are self-hosted). The browser content-security-policy is locked to self. The desktop app's one outbound connection is to github.com, to check for and download updates. It carries no project data; the update library adds a random identifier it creates once for this installation. The check can be switched off in Settings → Software update, and the app works fully offline.
  • No accounts, no sign-in. The desktop app opens straight into your workspace as a single local researcher — there is no login screen or password. Your operating-system account is the security boundary: on a shared computer, lock or sign out of it when you step away.
  • The database is encrypted at rest in the desktop app. Packaged builds encrypt the SQLite database with SQLCipher (AES-256), using a random per-install key held in your OS keychain (macOS Keychain / Windows DPAPI / Linux Secret Service) — a copied or synced database file is unreadable without it. If no OS keychain is available, the app says so plainly and runs unencrypted rather than storing a key insecurely. Two honest limits: only the database is encrypted — the documents and recordings you import are kept beside it as ordinary files, and inside a .mmbackup archive too — and encryption does not defend against software already running as your OS user; full-disk encryption (FileVault / BitLocker) is the answer for both. Development builds run from source use a plaintext database for inspectability. See SECURITY.md for the full posture. If your data is sensitive, also store it on an encrypted disk / user profile and keep backups somewhere correspondingly protected.

Tech stack

Layer Technology
Backend FastAPI + SQLAlchemy 2.0 (Python 3.12)
Database SQLite (Alembic migrations)
Frontend React 19 + Vite + TypeScript
UI shadcn/ui + Tailwind CSS v4
Data fetching TanStack Query
Rich text Tiptap
Charts Recharts
Statistics NumPy + SciPy + statsmodels (lazy-imported)
Parsing python-docx, pdfminer.six, tinytag, openpyxl, pyreadstat (all permissive)

Running from source

Most users should install the signed desktop build (see Install above). To develop, contribute, or build it yourself, run the backend and frontend together:

Prerequisites

  • Python 3.12+
  • Node.js 24+ (Active LTS)

Backend (FastAPI, port 8000):

cd backend
python -m venv venv && source venv/bin/activate   # Windows: venv\Scripts\activate
pip install -r requirements.txt
alembic upgrade head        # create / migrate the local SQLite database
uvicorn app.main:app --reload --port 8000

Frontend (Vite dev server, port 5173, proxies /api to the backend):

cd frontend
npm ci
npm run dev

Open http://localhost:5173. The app opens straight into the workspace as a local researcher (no account setup), and you can create your first project.

Configuration is via environment variables (all optional; sensible local defaults). Common ones — see backend/app/config.py for the full list:

Variable Default Purpose
MM_DATABASE_PATH dev.db SQLite database file
MM_DATA_DIR data Parent of documents/ and media/
MM_BACKUP_DIR backups Backup storage
MM_INACTIVITY_TIMEOUT_MINUTES 0 (off) Ends a session after this many idle minutes (e.g. 30); with no sign-in screen the app starts a new one at once, so it does not lock a shared machine
MM_COOKIE_SECURE false Set true when serving over HTTPS
MM_AUTO_BACKUP_INTERVAL_HOURS 4 Automatic backups are at least this many whole hours apart — one a few minutes after start when the last is older, then one per interval while running; 0 turns them off
MM_AUTO_BACKUP_MAX_COUNT 5 How many automatic backups are kept before the oldest is rotated out (at least 1)
MM_SESSION_EXPIRE_HOURS 24 Session lifetime (multi-user mode)
MM_CSRF_ENABLED true CSRF protection on state-changing requests; leave on

The bare names (INACTIVITY_TIMEOUT_MINUTES, COOKIE_SECURE) are still accepted as aliases, but the MM_-prefixed form takes precedence when both are set.

Tests

# Backend
cd backend && source venv/bin/activate
pip install -r requirements-dev.txt
python -m pytest tests/

# Frontend
cd frontend && npm test

See CONTRIBUTING.md for the full development workflow, migration guidance, and dependency policy.

Backups & data safety

Qualitative coding is irreplaceable manual work, so the app keeps several backup mechanisms: automatic pre-migration backups, automatic backups (a few minutes after the app opens whenever the last one is more than four hours old, then every four hours while it stays open), a snapshot taken when the app closes (on macOS and Linux — on Windows the app stops its engine at once when it quits, so the automatic backups are the cover there), and user-triggered .mmbackup archives (database + documents + media) with a validate-and-preview restore flow. Settings → Backup & Data → Backup history lists the .mmbackup archives, and any one can be restored, downloaded or deleted from there — restoring from that list is not held to the upload limit, because nothing is copied or uploaded; a backup file picked with Restore from a file is limited to 500 MB. In the desktop app a backup opens only in the installation that made it, so use .mmproject to move projects to another computer. Automatic backups exclude video recordings so a multi-gigabyte project doesn't multiply across the backup rotation — downloaded backups can include video, and restoring never deletes video files already on disk. The snapshot taken on closing is kept separately from the automatic ones, so restarting the app does not use up the automatic backups that cover your working day. Project exports (.mmproject) can likewise include or exclude recordings; a media-less archive re-imports cleanly, and a conversation's recording can be attached again afterwards (an observation's cannot yet). Before a merge or an overwrite changes a project, a full copy of it is written first — Settings → Backup & Data lists those safety copies with their project, date and size, and lets you download one to bring it back or delete the ones you no longer need. Back up regularly, and keep a copy off the working machine.

License

Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE. Copyright © 2026 George Chavez.

Mixed Measures is provided "as is", without warranty of any kind (see the license for the full disclaimer). It is a research aid, not a certified statistical authority: verify analyses against your own judgment and, where it matters, an independent tool before relying on them in deliverables or decisions.

"Mixed Measures" is used as a common-law trademark of the project author.

Citing Mixed Measures

If you use Mixed Measures in published work, please cite the version you analyzed with — it is part of what makes the analysis reproducible. GitHub renders a ready citation from CITATION.cff via Cite this repository in the sidebar, and the app itself offers copyable APA and BibTeX under Settings → About & citation.

Contributing & security

Support & expectations

Mixed Measures is built and maintained by one person. Bug reports and feature requests belong in Issues; questions, workflow help, and "is this a bug?" belong in Discussions. Expect a reply within a few business days — sometimes sooner, occasionally longer around a release. Security reports follow SECURITY.md and jump the queue.

About

Free, open-source desktop app for mixed methods research: qualitative coding and quantitative analysis in one local-first project. Code interviews, prepare and analyze survey data, and integrate both on a canvas.

Topics

Resources

Code of conduct

Contributing

Security policy

Accessibility

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages