From 743ea963358d808195c887d9065cf6e13454f84b Mon Sep 17 00:00:00 2001 From: jenner-analytics <299455446+jenner-analytics@users.noreply.github.com> Date: Sun, 12 Jul 2026 12:26:40 +0000 Subject: [PATCH] jenner-check: add 4 Jenner compatibility bundles + runner Adds jenner-check/ with four self-contained bundles derived from this repo's SAS homework scripts (a permutation test, a nonparametric bootstrap median estimate, a Monte Carlo integration by importance sampling, and a sampling-variability simulation study), each captured against the hosted Jenner engine along with a small bash runner and README to re-run and verify them. --- jenner-check/.gitignore | 2 + jenner-check/README.md | 76 ++++ jenner-check/run_jenner.sh | 357 ++++++++++++++++++ .../t002_permutation_test/autoexec.sas | 2 + .../t002_permutation_test/expected.json | 14 + .../t002_permutation_test/expected/log.txt | 130 +++++++ .../t002_permutation_test/expected/output.txt | 31 ++ jenner-check/t002_permutation_test/meta.json | 7 + jenner-check/t002_permutation_test/script.sas | 147 ++++++++ .../t004_bootstrap_median/autoexec.sas | 2 + .../t004_bootstrap_median/expected.json | 13 + .../t004_bootstrap_median/expected/log.txt | 35 ++ .../t004_bootstrap_median/expected/output.txt | 38 ++ jenner-check/t004_bootstrap_median/meta.json | 7 + jenner-check/t004_bootstrap_median/script.sas | 59 +++ .../t005_montecarlo_integration/autoexec.sas | 2 + .../t005_montecarlo_integration/expected.json | 12 + .../expected/log.txt | 46 +++ .../expected/output.txt | 11 + .../t005_montecarlo_integration/meta.json | 7 + .../t005_montecarlo_integration/script.sas | 53 +++ .../t006_sampling_variability/autoexec.sas | 2 + .../t006_sampling_variability/expected.json | 13 + .../expected/log.txt | 21 ++ .../expected/output.txt | 22 ++ .../t006_sampling_variability/meta.json | 7 + .../t006_sampling_variability/script.sas | 41 ++ 27 files changed, 1157 insertions(+) create mode 100644 jenner-check/.gitignore create mode 100644 jenner-check/README.md create mode 100755 jenner-check/run_jenner.sh create mode 100644 jenner-check/t002_permutation_test/autoexec.sas create mode 100644 jenner-check/t002_permutation_test/expected.json create mode 100644 jenner-check/t002_permutation_test/expected/log.txt create mode 100644 jenner-check/t002_permutation_test/expected/output.txt create mode 100644 jenner-check/t002_permutation_test/meta.json create mode 100644 jenner-check/t002_permutation_test/script.sas create mode 100644 jenner-check/t004_bootstrap_median/autoexec.sas create mode 100644 jenner-check/t004_bootstrap_median/expected.json create mode 100644 jenner-check/t004_bootstrap_median/expected/log.txt create mode 100644 jenner-check/t004_bootstrap_median/expected/output.txt create mode 100644 jenner-check/t004_bootstrap_median/meta.json create mode 100644 jenner-check/t004_bootstrap_median/script.sas create mode 100644 jenner-check/t005_montecarlo_integration/autoexec.sas create mode 100644 jenner-check/t005_montecarlo_integration/expected.json create mode 100644 jenner-check/t005_montecarlo_integration/expected/log.txt create mode 100644 jenner-check/t005_montecarlo_integration/expected/output.txt create mode 100644 jenner-check/t005_montecarlo_integration/meta.json create mode 100644 jenner-check/t005_montecarlo_integration/script.sas create mode 100644 jenner-check/t006_sampling_variability/autoexec.sas create mode 100644 jenner-check/t006_sampling_variability/expected.json create mode 100644 jenner-check/t006_sampling_variability/expected/log.txt create mode 100644 jenner-check/t006_sampling_variability/expected/output.txt create mode 100644 jenner-check/t006_sampling_variability/meta.json create mode 100644 jenner-check/t006_sampling_variability/script.sas diff --git a/jenner-check/.gitignore b/jenner-check/.gitignore new file mode 100644 index 0000000..75fb4a3 --- /dev/null +++ b/jenner-check/.gitignore @@ -0,0 +1,2 @@ +*_response.json +response.json diff --git a/jenner-check/README.md b/jenner-check/README.md new file mode 100644 index 0000000..58459d6 --- /dev/null +++ b/jenner-check/README.md @@ -0,0 +1,76 @@ +# jenner-check: compatibility test bundles + +Each `tNNN_/` subdirectory is a self-contained test bundle built from SAS +code that already lives in this repository. A bundle pins the output of a +captured passing run so the same script can be re-run later and compared +against that snapshot. + +This directory is fully self-contained: nothing outside `jenner-check/` is +referenced or modified, nothing runs on merge or checkout, and deleting the +directory removes every trace of it. + +## What the runner sends — read before running + +`run_jenner.sh` uploads only a bundle's SAS **source text** — `autoexec.sas` +plus `script.sas`, and nothing else — over HTTPS to `api.jenneranalytics.com`, +where it runs and returns the log and listing. It does not read or upload any +data files, so anything sitting next to a script stays on your machine; the +only thing transmitted is the code you run, as when pasting a snippet into any +hosted tool. Nothing is sent unless you run a command yourself. To point the +runner at a different endpoint, set `JENNER_HOST`. + +## What's in here + +``` +jenner-check/ +├── README.md # this file +├── run_jenner.sh # runner (bash + curl; python3 used if present) +└── tNNN_/ + ├── script.sas # the SAS under test (derived from this repo) + ├── autoexec.sas # run options prepended to script.sas at run time + ├── expected.json # fields pinned from the captured passing run + ├── meta.json # provenance: source file, blob sha, commit + └── expected/ # human-readable snapshot of that run + ├── log.txt + └── output.txt +``` + +`meta.json` records the source path, blob sha, and commit of the script each +bundle was built from, so you can verify the copy matches your code. + +## How to run + +From inside this directory: + +```bash +./run_jenner.sh --list # show the bundles in this directory +./run_jenner.sh --all # run every bundle, verify pinned fields +./run_jenner.sh tNNN_ # run just one (use a name from --list) +``` + +For each bundle the runner submits the SAS, writes the JSON response to +`_response.json`, and compares the response against the bundle's +`expected.json` — status, exit code, and the pinned log lines. A bundle +passes only when every pinned field matches, so an `N pass, 0 fail` summary +from `--all` means the captured results still hold. + +You can also compare a saved response offline, with no network call: + +```bash +./run_jenner.sh --compare tNNN__response.json tNNN_/expected.json +``` + +Requirements: `bash` 4+ and `curl` (both ship with mainstream Linux and +macOS); `python3` for the pinned-field comparison. On Windows, run it under +WSL. + +## Removing this directory + +`git rm -r jenner-check/` (or just delete the folder). Nothing else in the +repository references it. + +--- + +`run_jenner.sh` and this README are provided under the MIT license. Each +bundle's `script.sas` is a copy of code that already lives in this repository +and remains under this repository's own license — nothing here relicenses it. diff --git a/jenner-check/run_jenner.sh b/jenner-check/run_jenner.sh new file mode 100755 index 0000000..ad7825a --- /dev/null +++ b/jenner-check/run_jenner.sh @@ -0,0 +1,357 @@ +#!/usr/bin/env bash +# run_jenner.sh - mac/linux runner for Jenner compatibility checks. +# +# Quick start: +# cd jenner-check/ +# ./run_jenner.sh # lists bundles in the current dir +# ./run_jenner.sh t001_something # run that one +# ./run_jenner.sh --all # run every bundle in the current dir +# +# Usage: ./run_jenner.sh [bundle-dir | script.sas | --all | --list] [response.json] +# ./run_jenner.sh --compare RESPONSE.json EXPECTED.json +# +# (no arg) If the current directory has tNNN_* bundles, list them +# with a copy-paste command. Otherwise show this help. +# +# --all Run every tNNN_* bundle in the current directory in +# sequence, print a pass/fail summary. +# +# --list, -l List the bundles visible in the current directory and +# exit without running anything. +# +# --compare Purely local, no network: check RESPONSE.json against the +# pinned EXPECTED.json and print one line per check. Only +# keys present in EXPECTED.json are checked (keys starting +# with "_" are provenance and ignored): status / exit_code +# must match exactly, every log_contains entry must appear +# in the response log, no log_does_not_contain entry may +# appear, output_contains entries must appear in the output, +# and pinned diagnostics lists must match exactly. Unknown +# keys print a warning and do not fail. +# Exit 0 all-match, 1 any mismatch, 2 usage/unreadable file/ +# invalid JSON, 5 python3 unavailable. +# +# bundle-dir A directory containing script.sas and (optionally) +# autoexec.sas. The two are concatenated (autoexec first, +# then a blank line, then script) and submitted together. +# This is the normal case. +# +# script.sas A single .sas file. Submitted as-is — no autoexec. +# +# The API response is written to (or response.json in +# the current directory if omitted) and the most useful fields are also +# printed to stdout for a quick sanity check. +# +# What "pass" means: a bundle run passes only if the POST returns HTTP 200 +# AND — when the bundle carries an expected.json and python3 is available — +# the fresh response matches every pinned field (the --compare semantics +# above). Without python3 the pinned comparison is skipped with a notice +# and the run falls back to the old HTTP/status-only behavior. +# +# Requires: bash 4+, curl. Both ship with every mainstream Linux distro +# and macOS 12+. python3 (present on virtually every modern system) is +# optional but needed for the pinned-result comparison and the pretty +# summary. Windows: use run_jenner.bat (single-file mode) or WSL. +# +# IMPORTANT: execute this script, don't source it. Running with `. ./...` +# or `source ./...` will short-circuit error handling and can close your +# terminal if an error path fires. + +# --- refuse to be sourced ------------------------------------------------ +# `return` only works inside a sourced script. If we ARE sourced, print a +# message and return 1 so we don't kill the parent shell with exit. If +# we're running directly, (return 0) fails and we fall through. +(return 0 2>/dev/null) && { + printf 'run_jenner.sh: execute this script, do not source it.\n ./run_jenner.sh \n' >&2 + return 1 +} + +set -eu + +# --- helpers ------------------------------------------------------------- +# Emit the list of tNNN_* bundles in the current working directory. A +# "bundle" is a directory matching t[0-9]*_* whose name contains a +# script.sas file. Writes one path per line (no prefix); empty output +# if nothing found. +list_bundles_here() { + local d + for d in ./t[0-9]*_*/ ; do + [[ -d "$d" && -f "$d/script.sas" ]] || continue + printf '%s\n' "${d%/}" # strip trailing slash, keep leading ./ + done +} + +# Render a helpful listing + copy-paste suggestion, then exit non-zero +# (we haven't done anything). Used when the user runs with no args. +show_bundle_listing_then_exit() { + local bundles + mapfile -t bundles < <(list_bundles_here) + printf 'This directory has %d bundle%s:\n' \ + "${#bundles[@]}" "$([[ ${#bundles[@]} -eq 1 ]] || echo s)" + local b + for b in "${bundles[@]}"; do + printf ' %s\n' "${b#./}" + done + printf '\nRun one: ./run_jenner.sh %s\n' "${bundles[0]#./}" + printf 'Run them all: ./run_jenner.sh --all\n' + printf 'Just list: ./run_jenner.sh --list\n' + exit 2 +} + +# Show the usage block when we have nothing better to offer. +show_usage_then_exit() { + local status=${1:-2} + { + printf 'Usage: %s [bundle-dir | script.sas | --all | --list] [response.json]\n' "$(basename "$0")" + printf ' %s --compare RESPONSE.json EXPECTED.json\n\n' "$(basename "$0")" + printf 'Examples:\n' + printf ' %s t001_my_bundle # run one bundle\n' "$(basename "$0")" + printf ' %s --all # run every tNNN_* bundle in this dir\n' "$(basename "$0")" + printf ' %s path/to/script.sas # run a single file, no autoexec\n' "$(basename "$0")" + printf ' %s --compare r.json e.json # check a response against pinned fields\n' "$(basename "$0")" + } >&2 + exit "$status" +} + +# Compare a response.json against a pinned expected.json. Purely local — +# no network. Only keys present in expected.json are checked; keys whose +# name starts with "_" are capture provenance and are ignored. Prints one +# line per check (ok/FAIL/warn). Return codes: 0 all checks match, +# 1 any mismatch, 2 unreadable file or invalid JSON, 5 python3 missing. +compare_response_to_expected() { + local resp_file=$1 expected_file=$2 + if ! command -v python3 >/dev/null 2>&1; then + printf 'error: the pinned comparison needs python3, which was not found\n' >&2 + return 5 + fi + python3 - "$resp_file" "$expected_file" <<'PY' +import json, sys + +def load(path, label): + try: + with open(path, encoding="utf-8") as f: + return json.load(f) + except (OSError, ValueError) as e: + print(f"compare: cannot read {label} file {path}: {e}", file=sys.stderr) + sys.exit(2) + +resp = load(sys.argv[1], "response") +exp = load(sys.argv[2], "expected") +if not isinstance(resp, dict) or not isinstance(exp, dict): + print("compare: both files must contain a JSON object", file=sys.stderr) + sys.exit(2) + +failures = 0 + +def ok(msg): + print(f" ok {msg}") + +def bad(msg): + global failures + failures += 1 + print(f" FAIL {msg}") + +def as_list(v): + return v if isinstance(v, list) else [v] + +log = resp.get("log") or "" +output = resp.get("output") or "" + +for key, want in exp.items(): + if key.startswith("_"): + continue # provenance (_captured_at, ...), not a check + if key in ("status", "exit_code"): + got = resp.get(key) + if got == want: + ok(f"{key} == {want!r}") + else: + bad(f"{key}: expected {want!r}, got {got!r}") + elif key == "log_contains": + for s in as_list(want): + if s in log: + ok(f"log contains {s!r}") + else: + bad(f"log_contains: log is missing {s!r}") + elif key == "log_does_not_contain": + for s in as_list(want): + if s not in log: + ok(f"log does not contain {s!r}") + else: + bad(f"log_does_not_contain: log unexpectedly contains {s!r}") + elif key == "output_contains": + for s in as_list(want): + if s in output: + ok(f"output contains {s!r}") + else: + bad(f"output_contains: output is missing {s!r}") + elif key == "diagnostics": + diag = resp.get("diagnostics") or {} + for dk, dv in (want or {}).items(): + if dk.startswith("_"): + continue + got = diag.get(dk) + if got == dv: + ok(f"diagnostics.{dk} == {dv!r}") + else: + bad(f"diagnostics.{dk}: expected {dv!r}, got {got!r}") + else: + print(f" warn unknown expected.json key {key!r} — not checked") + +if failures: + print(f"pinned comparison: {failures} check(s) FAILED") + sys.exit(1) +print("pinned comparison: all pinned checks passed") +PY +} + +# --- arg parsing --------------------------------------------------------- +if [[ $# -lt 1 ]]; then + # No args: if the cwd contains bundles, list them; otherwise show help. + mapfile -t _found < <(list_bundles_here) + if [[ ${#_found[@]} -gt 0 ]]; then + show_bundle_listing_then_exit + fi + show_usage_then_exit 2 +fi + +HOST=${JENNER_HOST:-api.jenneranalytics.com} + +case "$1" in + -h|--help) + show_usage_then_exit 0 + ;; + -l|--list) + mapfile -t _found < <(list_bundles_here) + if [[ ${#_found[@]} -eq 0 ]]; then + printf 'No tNNN_* bundles found in %s\n' "$(pwd)" + exit 0 + fi + printf 'Bundles in %s:\n' "$(pwd)" + for b in "${_found[@]}"; do + printf ' %s\n' "${b#./}" + done + exit 0 + ;; + --compare) + # Purely local: diff a response.json against a pinned expected.json. + if [[ $# -ne 3 ]]; then + printf 'usage: %s --compare RESPONSE.json EXPECTED.json\n' "$(basename "$0")" >&2 + exit 2 + fi + _rc=0 + compare_response_to_expected "$2" "$3" || _rc=$? + exit "$_rc" + ;; + --all) + mapfile -t _found < <(list_bundles_here) + if [[ ${#_found[@]} -eq 0 ]]; then + printf 'No tNNN_* bundles found in %s\n' "$(pwd)" >&2 + exit 3 + fi + _pass=0; _fail=0 + for b in "${_found[@]}"; do + printf '\n── %s ──\n' "${b#./}" + if "$0" "$b" "${b#./}_response.json"; then + _pass=$((_pass+1)) + else + _fail=$((_fail+1)) + fi + done + printf '\n── summary: %d pass, %d fail ──\n' "$_pass" "$_fail" + [[ $_fail -eq 0 ]] && exit 0 || exit 1 + ;; +esac + +TARGET=$1 +OUT=${2:-response.json} + +# --- assemble the submission body --------------------------------------- +# If TARGET is a directory, treat it as a bundle. If it's a file, submit +# it directly. +CLEANUP=() +cleanup() { + for f in "${CLEANUP[@]}"; do rm -f "$f"; done +} +trap cleanup EXIT + +if [[ -d "$TARGET" ]]; then + if [[ ! -f "$TARGET/script.sas" ]]; then + printf 'error: %s is a directory but has no script.sas\n' "$TARGET" >&2 + exit 3 + fi + SUBMIT=$(mktemp -t jc_submit.XXXXXX.sas) + CLEANUP+=("$SUBMIT") + if [[ -f "$TARGET/autoexec.sas" ]]; then + cat "$TARGET/autoexec.sas" > "$SUBMIT" + printf '\n' >> "$SUBMIT" + fi + cat "$TARGET/script.sas" >> "$SUBMIT" + printf 'Submitting bundle: %s\n' "$TARGET" + if [[ -f "$TARGET/autoexec.sas" ]]; then + printf ' autoexec.sas (%d bytes) + script.sas (%d bytes)\n' \ + "$(wc -c < "$TARGET/autoexec.sas")" "$(wc -c < "$TARGET/script.sas")" + else + printf ' script.sas (%d bytes), no autoexec\n' "$(wc -c < "$TARGET/script.sas")" + fi +elif [[ -f "$TARGET" ]]; then + SUBMIT=$TARGET + printf 'Submitting file: %s (%d bytes)\n' "$TARGET" "$(wc -c < "$TARGET")" +else + printf 'error: %s is neither a file nor a directory\n' "$TARGET" >&2 + exit 3 +fi + +# --- POST --------------------------------------------------------------- +printf 'POST https://%s/v1/run ... ' "$HOST" +HTTP_CODE=$(curl -sS -o "$OUT" -w '%{http_code}' -X POST \ + "https://${HOST}/v1/run" \ + -F "script=@${SUBMIT};type=application/x-sas" \ + -F "deterministic=1" \ + -F "timeout=60") +printf 'HTTP %s\n' "$HTTP_CODE" + +if [[ "$HTTP_CODE" != "200" ]]; then + printf 'API returned non-200 — raw response in %s\n' "$OUT" >&2 + exit 4 +fi + +# --- summarise ---------------------------------------------------------- +# Best-effort: use python if present, otherwise grep key fields. +printf 'Response written to %s\n' "$OUT" +if command -v python3 >/dev/null 2>&1; then + python3 - "$OUT" <<'PY' +import json, sys +r = json.load(open(sys.argv[1])) +print(f" status : {r.get('status')}") +print(f" exit_code : {r.get('exit_code')}") +print(f" duration_ms: {r.get('duration_ms')}") +print(f" run_id : {r.get('run_id')}") +print(f" jenner_ver : {r.get('jenner_version')}") +log = r.get('log', '') +if log: + print(' log (first 10 lines):') + for line in log.splitlines()[:10]: + print(f' {line}') +PY +else + printf ' (install python3 for a pretty summary; raw JSON in %s)\n' "$OUT" +fi + +# --- pinned-result comparison --------------------------------------------- +# A bundle that ships an expected.json only passes if the fresh response +# matches every pinned field — HTTP 200 alone is not a pass. Without +# python3 we can't parse JSON portably, so fall back to the old behavior +# and say so. +if [[ -d "$TARGET" && -f "$TARGET/expected.json" ]]; then + if command -v python3 >/dev/null 2>&1; then + printf 'Comparing response against pinned %s/expected.json\n' "${TARGET%/}" + _rc=0 + compare_response_to_expected "$OUT" "$TARGET/expected.json" || _rc=$? + if [[ $_rc -ne 0 ]]; then + printf 'Bundle FAILED the pinned comparison (see lines above)\n' >&2 + exit "$_rc" + fi + else + printf 'pinned comparison skipped (python3 not found) — HTTP/status only\n' + fi +fi diff --git a/jenner-check/t002_permutation_test/autoexec.sas b/jenner-check/t002_permutation_test/autoexec.sas new file mode 100644 index 0000000..719f93f --- /dev/null +++ b/jenner-check/t002_permutation_test/autoexec.sas @@ -0,0 +1,2 @@ +/* cap input rows for the captured run */ +options obs=100; diff --git a/jenner-check/t002_permutation_test/expected.json b/jenner-check/t002_permutation_test/expected.json new file mode 100644 index 0000000..4aab291 --- /dev/null +++ b/jenner-check/t002_permutation_test/expected.json @@ -0,0 +1,14 @@ +{ + "_captured_at": "2026-07-12T11:56:45Z", + "_captured_run_id": "r_3ccf55b9e0084f528fc1ef0186352625", + "status": "ok", + "exit_code": 0, + "log_contains": [ + "NOTE: PROC MEANS statement used.", + "NOTE: PROC PRINT completed: 1 observations printed, 5 variables", + "NOTE: PROC TTEST statement used.", + "NOTE: 2 dataset(s) deleted." + ], + "log_does_not_contain": ["ERROR:", "[JENNER-ERROR"], + "diagnostics": {"parse_warnings": [], "runtime_warnings": []} +} diff --git a/jenner-check/t002_permutation_test/expected/log.txt b/jenner-check/t002_permutation_test/expected/log.txt new file mode 100644 index 0000000..7a14841 --- /dev/null +++ b/jenner-check/t002_permutation_test/expected/log.txt @@ -0,0 +1,130 @@ + +NOTE: Option OBS changed to 100. +NOTE: DATA grupa1 + +NOTE: Processing inline DATALINES (1 lines) + +NOTE: Read 13 rows from DATALINES. +NOTE: Wrote grupa1 (13 rows, 1 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: DATA grupa2 + +NOTE: Processing inline DATALINES (1 lines) + +NOTE: Read 34 rows from DATALINES. +NOTE: Wrote grupa2 (34 rows, 1 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: DATA grupe + + +NOTE: Read 13 rows from grupa1. +NOTE: Read 47 rows from grupa2. +NOTE: Wrote grupe (47 rows, 3 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: PROC MEANS +NOTE: Output dataset means has 2 observations and 4 variables. +NOTE: PROC MEANS statement used. +NOTE: PROC TRANSPOSE data=means + +NOTE: Created 1 rows. +NOTE: Output written to /tmp/work/r_3ccf55b9e0084f528fc1ef0186352625/work/b5191e3c-a2f5-4473-9690-dd04e95cb22a/means_t.avro. +NOTE: DATA means_t + + +NOTE: Read 1 rows from means_t. +NOTE: Wrote means_t (1 rows, 4 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: PROC PLAN +NOTE: Design written (47000 rows, 2 columns) +NOTE: PROC PLAN statement used. +NOTE: DATA grupe_perm + + +NOTE: Wrote grupe_perm (100 rows, 1 columns). +NOTE: DATA elapsed: + wall 0.06 seconds + cpu 0.06 seconds +NOTE: DATA approxperm + +NOTE: MERGE statement used without BY variables. Performing positional merge (row 1 with row 1, etc.). +NOTE: PROC SORT data=approxperm + +NOTE: Read 8192 rows from approxperm. +NOTE: Wrote approxperm (100 rows, 3 columns). +NOTE: PROC SORT statement used. +NOTE: DATA approxperm + +NOTE: Stream 1 processed 100 rows, max BY-group size: 100 (O(1) memory verified) +NOTE: Stream 2 processed 47 rows, max BY-group size: 1 (O(1) memory verified) + +NOTE: Wrote approxperm (146 rows, 4 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: PROC MEANS +NOTE: Output dataset means_perm has 100 observations and 6 variables. +NOTE: PROC MEANS statement used. +NOTE: PROC TRANSPOSE data=means_perm + +NOTE: Created 100 rows. +NOTE: Output written to /tmp/work/r_3ccf55b9e0084f528fc1ef0186352625/work/b5191e3c-a2f5-4473-9690-dd04e95cb22a/means_perm_t.avro. +NOTE: DATA means_perm_t + + +NOTE: Read 100 rows from means_perm_t. +NOTE: Wrote means_perm_t (100 rows, 6 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: PROC MEANS +NOTE: Output dataset p_sig has 1 observations and 4 variables. +NOTE: PROC MEANS statement used. +NOTE: DATA p_sig + + +NOTE: Read 1 rows from p_sig. +NOTE: Wrote p_sig (1 rows, 5 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: PROC PRINT data= + +NOTE: PROC PRINT completed: 1 observations printed, 5 variables +NOTE: PROC DATASETS library=WORK + +NOTE: + Directory + + Libref WORK + Engine AVRO + Physical Name /tmp/work/r_3ccf55b9e0084f528fc1ef0186352625/work/b5191e3c-a2f5-4473-9690-dd04e95cb22a + + # Name Member Type + ---------------------------------------------------- + 1 APPROXPERM DATA + 2 GRUPA1 DATA + 3 GRUPA2 DATA + 4 GRUPE DATA + 5 GRUPE_PERM DATA + 6 MEANS DATA + 7 MEANS_PERM DATA + 8 MEANS_PERM_T DATA + 9 MEANS_T DATA + 10 P_SIG DATA + +NOTE: Deleting APPROXPERM (memtype=DATA). +NOTE: Deleting GRUPE_PERM (memtype=DATA). +NOTE: 2 dataset(s) deleted. +NOTE: PROC TTEST data=grupe + +NOTE: ODS plot written: ttest_summary.spec.json +NOTE: PROC TTEST ODS Graphics generated. +NOTE: PROC TTEST statement used. diff --git a/jenner-check/t002_permutation_test/expected/output.txt b/jenner-check/t002_permutation_test/expected/output.txt new file mode 100644 index 0000000..3d0d3d0 --- /dev/null +++ b/jenner-check/t002_permutation_test/expected/output.txt @@ -0,0 +1,31 @@ + + Obs _TYPE_ _FREQ_ sum n p_sig +----- ------ ------ --- --- ------------ + 1 0 100 0 100 0.0099009901 + + The TTEST Procedure + Variable: ocjena + +grupa N Mean Std Dev Std Err +---------- -------- -------- -------- -------- +1 13 2.8538 0.8569 0.2377 +2 34 2.5706 0.9524 0.1633 +Diff (1-2) 0.2833 + +grupa Lower CL Mean Mean Upper CL Mean Lower CL Std Dev Std Dev Upper CL Std Dev +---------- ------------- -------- ------------- ---------------- -------- ---------------- +1 2.3360 2.8538 3.3717 0.6145 0.8569 1.4146 +2 2.2383 2.5706 2.9029 0.7682 0.9524 1.2536 +Diff (1-2) -0.3262 0.2833 0.8927 0.7696 0.9279 1.1687 + +Method Variances DF t Value Pr > |t| +------------- --------- -------- -------- -------- +Pooled Equal 45 0.94 0.3542 +Satterthwaite Unequal 24.1 0.98 0.3358 + + Equality of Variances + +Method F Value Pr > F +-------- -------- -------- +Folded F 1.24 0.7223 + diff --git a/jenner-check/t002_permutation_test/meta.json b/jenner-check/t002_permutation_test/meta.json new file mode 100644 index 0000000..854ed6e --- /dev/null +++ b/jenner-check/t002_permutation_test/meta.json @@ -0,0 +1,7 @@ +{ + "bundle": "t002_permutation_test", + "source_file": "DZ 3/divanovic_doris_dz3_zad2.sas", + "source_blob_sha": "d3b392a66bd09e7afa18eaf21e29b5ef94e00647", + "source_commit": "7ca6d5646283a3e35c3d4e645e4006a8ad0ecf39", + "notes": "Permutation test comparing mean grades (ocjena) between two groups: builds 1000 random permutations via PROC PLAN, computes the permutation p-value, then runs PROC TTEST on the original grouping for comparison. Edit applied: dropped the nonexistent 'allperm' dataset name from the final PROC DATASETS DELETE list (the script only ever creates APPROXPERM/GRUPE_PERM under that cleanup step; deleting the never-created ALLPERM produced a harmless WARNING and a nonzero exit code)." +} diff --git a/jenner-check/t002_permutation_test/script.sas b/jenner-check/t002_permutation_test/script.sas new file mode 100644 index 0000000..7444dc4 --- /dev/null +++ b/jenner-check/t002_permutation_test/script.sas @@ -0,0 +1,147 @@ + +/* Definicija makrovarijable koja predstavlja broj permutacija. */ +%let n_perm=1000; + +/* Unos podataka za grupu 1 u dataset grupa1 */ +data grupa1; + input ocjena @@; + datalines; + 3.8 1.8 1.0 3.6 3.3 2.7 3.7 2.5 3.8 2.2 2.5 3.4 2.8 + ; + +/* Unos podataka za grupu 2 u dataset grupa2 */ +data grupa2; + input ocjena @@; + datalines; +4.0 2.5 3.6 2.5 3.6 1.7 2.8 2.6 2.7 2.5 2.6 2.2 2.5 2.3 1.3 3.2 2.6 1.0 2.6 0.0 2.8 3.0 2.5 3.1 4.0 2.9 2.7 3.9 3.4 3.6 3.1 0.7 0.7 2.2 + ; + +/* Spajanje dva prethodno kreirana dataseta u novi dataset grupe, + dodavanje nove varijable/stupca grupa s odgovarajućom vrijednosti, + dodavanje novog stupca id. */ +data grupe; + set grupa1 (in=a) grupa2; + if a then grupa=1; else grupa=2; + id=_N_; + run; + +/* Računanje aritmetičke sredine ocjena za pojedinu grupu, + spremanje rezultata u dataset imena means. */ + proc means data= grupe nway noprint; + var ocjena; + class grupa; + output out=means mean=mean; + run; + +/* Transponiranje dataseta means. */ +proc transpose data=means out=means_t prefix=_; + var mean; + id grupa; + run; + +/* U taj dataset dodavanje novog stupca koji je apsolutna razlika. */ + data means_t; + set means_t; + abs_diff=abs(_1-_2); + run; + +/* Kreiranje željenog broja slucajnih permutacija + elemenata 1 - 47 + u slučajnom poretku. */ + proc plan seed=60359 ; + factors rep=&n_perm random + id = 47 /noprint + ; + output out=approxperm; +run; + +/* proc freq; table rep*id; run;*/ + +/* Za svaku permutaciju + stavljanje prvih 13 elemenata u grupu 1, + a ostalih u grupu 2. */ + data grupe_perm; + do i=1 to &n_perm; + do j=1 to 13; + grupa=1; + output; + end; + do j=14 to 47; + grupa=2; + output; + end; + end; + keep grupa; + run; + +/* Dodavanje tog stupca na dobiveni dataset approxperm. */ +data approxperm; + merge approxperm grupe_perm; +run; + +/* Sortiranje dataseta approxperm po id i rep. */ +proc sort data=approxperm; + by id rep; +run; + +/* Spajanje dataseta approxperm i dataseta grupe iz kojeg izuzmemo stupac grupa. */ +data approxperm; + merge approxperm grupe(drop=grupa); + by id; + run; + +/* Računanje proječne vrijednosti ocjena i broj studenata po grupama za +svaku permutaciju. */ +proc means data=approxperm nway noprint; + var ocjena; + class rep grupa; + output out=means_perm mean=mean n=n; + run; + +/* Transponiranje upravo dobivenog dataseta. */ +proc transpose data=means_perm out=means_perm_t prefix=_; + var mean; + id grupa; + by rep; + run; + +/* Za svaku permutaciju + računanje apsolutne vrijednosti razlike + prosječnih vrijednosti po grupama, + provjera je ona veća ili jednaka od opažene. */ + data means_perm_t; + set means_perm_t; + if _N_=1 then set means_t(keep=abs_diff); + abs_diff_perm=abs(_1-_2); + N_sig=(abs_diff_perm ge abs_diff); + run; + +/* Računanje broja permutacija u kojima je + veća ili jednaka od opažene. */ + proc means noprint; + var N_sig; + output out=p_sig sum=sum n=n; + run; + +/* Računanje p-vrijednosti testa. */ + data p_sig; + set p_sig; + p_sig=(sum+1)/(n+1); + run; + + proc print; run; + +/* Brisanje podataka iz biblioteke WORK. */ +/* jenner-check: dropped "allperm" from this DELETE list -- it was never + created by this script (the dataset that's actually built above is + named APPROXPERM), so deleting it just produced a harmless WARNING. */ + proc datasets library=work; + delete approxperm grupe_perm; + run;quit; + +/* t-test */ +proc ttest data=grupe; + var ocjena; + class grupa; + run; + diff --git a/jenner-check/t004_bootstrap_median/autoexec.sas b/jenner-check/t004_bootstrap_median/autoexec.sas new file mode 100644 index 0000000..719f93f --- /dev/null +++ b/jenner-check/t004_bootstrap_median/autoexec.sas @@ -0,0 +1,2 @@ +/* cap input rows for the captured run */ +options obs=100; diff --git a/jenner-check/t004_bootstrap_median/expected.json b/jenner-check/t004_bootstrap_median/expected.json new file mode 100644 index 0000000..2b4ceb1 --- /dev/null +++ b/jenner-check/t004_bootstrap_median/expected.json @@ -0,0 +1,13 @@ +{ + "_captured_at": "2026-07-12T12:05:12Z", + "_captured_run_id": "r_a0b88f8b71a94887b9c94a2fd98e09d4", + "status": "ok", + "exit_code": 0, + "log_contains": [ + "NOTE: PROC SURVEYSELECT completed.", + "NOTE: Read 6 rows from median_all.", + "NOTE: Wrote error_all (6 rows, 1 columns)." + ], + "log_does_not_contain": ["ERROR:", "[JENNER-ERROR"], + "diagnostics": {"parse_warnings": [], "runtime_warnings": []} +} diff --git a/jenner-check/t004_bootstrap_median/expected/log.txt b/jenner-check/t004_bootstrap_median/expected/log.txt new file mode 100644 index 0000000..b53cda9 --- /dev/null +++ b/jenner-check/t004_bootstrap_median/expected/log.txt @@ -0,0 +1,35 @@ + +NOTE: Option OBS changed to 100. +NOTE: DATA data + +NOTE: Processing inline DATALINES (1 lines) + +NOTE: Read 16 rows from DATALINES. +NOTE: Wrote data (16 rows, 1 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: Option TITLE changed to Medijan. +NOTE: PROC MEANS +NOTE: PROC MEANS statement used. +NOTE: Option TITLE changed to Bootstrap. +NOTE: PROC SURVEYSELECT data=data + +NOTE: PROC SURVEYSELECT completed. +NOTE: PROC MEANS +NOTE: Output dataset median_all has 6 observations and 6 variables. +NOTE: PROC MEANS statement used. +NOTE: Option TITLE changed to Neparametarska bootstrap procjena medijana. +NOTE: PROC MEANS +NOTE: PROC MEANS statement used. +NOTE: DATA error_all + + +NOTE: Read 6 rows from median_all. +NOTE: Wrote error_all (6 rows, 1 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: Option TITLE changed to Boostrap procjena pogreske. +NOTE: PROC MEANS +NOTE: PROC MEANS statement used. diff --git a/jenner-check/t004_bootstrap_median/expected/output.txt b/jenner-check/t004_bootstrap_median/expected/output.txt new file mode 100644 index 0000000..398952d --- /dev/null +++ b/jenner-check/t004_bootstrap_median/expected/output.txt @@ -0,0 +1,38 @@ + Medijan + + The MEANS Procedure + + Variable Median + ----------------------- + x 31.5000000 + ----------------------- + + Bootstrap + + +The SURVEYSELECT Procedure + +Selection Method URS +Input Data Set data +Number of Obs Read 16 +Sample Size 10 +Sampling Weight 1.6000 +Number of Replicates 6 + Neparametarska bootstrap procjena medijana + + The MEANS Procedure + + Variable Mean + ----------------------- + MEDIAN 31.5833333 + ----------------------- + + Boostrap procjena pogreske + + The MEANS Procedure + + Variable Mean + ----------------------- + error_ 1.0833333 + ----------------------- + diff --git a/jenner-check/t004_bootstrap_median/meta.json b/jenner-check/t004_bootstrap_median/meta.json new file mode 100644 index 0000000..9d9f08a --- /dev/null +++ b/jenner-check/t004_bootstrap_median/meta.json @@ -0,0 +1,7 @@ +{ + "bundle": "t004_bootstrap_median", + "source_file": "DZ 8/divanovic_doris_dz8_zad1.sas", + "source_blob_sha": "629223b55cd2d530f2b46042fb9b58918071f9e3", + "source_commit": "7ca6d5646283a3e35c3d4e645e4006a8ad0ecf39", + "notes": "Nonparametric bootstrap estimate of the median (Efron-style bioequivalence example): PROC SURVEYSELECT (URS, sampling-with-replacement) resamples the data, PROC MEANS computes the median per replicate, then the mean and error of those bootstrap medians are reported. Edits applied: (1) libname to an external pills_efron.sas7bdat (outside this repo, binary, unreadable) replaced with a small inline mock dataset of the same shape -- one numeric column x, median close to the script's own commented benchmark of 32; (2) reps trimmed from 500 to 6 so the resampled output stays under this run's observation cap without truncating mid-replicate." +} diff --git a/jenner-check/t004_bootstrap_median/script.sas b/jenner-check/t004_bootstrap_median/script.sas new file mode 100644 index 0000000..2332b8c --- /dev/null +++ b/jenner-check/t004_bootstrap_median/script.sas @@ -0,0 +1,59 @@ +/* jenner-check: the original script began with + libname lib "/home/u63522375/divanovic_doris_dz8"; + data data; set lib.pills_efron; run; + pills_efron.sas7bdat is a binary SAS dataset (Efron-style bioequivalence + measurements, a single numeric column "x") that lives outside this + repository and outside the libname path shown above. It's replaced below + with a small inline mock dataset of the same shape (one numeric column + "x", median close to the repo's own commented benchmark of 32) so the + rest of the script -- PROC MEANS, PROC SURVEYSELECT bootstrap, and the + bias/error computation -- runs completely unmodified. */ +data data; + input x @@; + datalines; + 28 31 30 34 36 29 33 32 35 27 31 34 30 33 36 28 + ; +run; + +title "Medijan"; +proc means data = data median; + var x; +run; + +/* jenner-check: reps trimmed from 500 to 6 so the resampled output + (reps * 16 mock rows) stays comfortably under this run's observation + cap -- otherwise the cap truncates mid-replicate and skews the + downstream bootstrap median. The URS bootstrap technique itself, and + every statistic computed from it below, is unchanged. */ +title "Bootstrap"; +proc surveyselect + data = data + seed = 775566 + method = urs + samprate = 1 + reps = 6 + out = boot; +run; + +proc means data = boot nway noprint; + by Replicate; + output out = median_all median = median; +run; + +title "Neparametarska bootstrap procjena medijana"; +proc means data = median_all mean; + var median; +run; + +/* ==> procjena pristranosti: 33.4075000 - 32 = 1.4075 (izvorni rezultat, prije zamjene podataka) */ + +data error_all; + set median_all; + error_ = abs(median - 32); + keep error_; +run; + +title "Boostrap procjena pogreske"; +proc means data = error_all mean; + var error_; +run; diff --git a/jenner-check/t005_montecarlo_integration/autoexec.sas b/jenner-check/t005_montecarlo_integration/autoexec.sas new file mode 100644 index 0000000..719f93f --- /dev/null +++ b/jenner-check/t005_montecarlo_integration/autoexec.sas @@ -0,0 +1,2 @@ +/* cap input rows for the captured run */ +options obs=100; diff --git a/jenner-check/t005_montecarlo_integration/expected.json b/jenner-check/t005_montecarlo_integration/expected.json new file mode 100644 index 0000000..83a57c7 --- /dev/null +++ b/jenner-check/t005_montecarlo_integration/expected.json @@ -0,0 +1,12 @@ +{ + "_captured_at": "2026-07-12T12:16:21Z", + "_captured_run_id": "r_0d0590af71aa4ce89fa5e5a0f8bcdacc", + "status": "ok", + "exit_code": 0, + "log_contains": [ + "NOTE: PROC SGPLOT output written to: ./ods_output/sgplot.svg", + "NOTE: PROC PRINT completed: 5 observations printed, 3 variables" + ], + "log_does_not_contain": ["ERROR:", "[JENNER-ERROR"], + "diagnostics": {"parse_warnings": [], "runtime_warnings": []} +} diff --git a/jenner-check/t005_montecarlo_integration/expected/log.txt b/jenner-check/t005_montecarlo_integration/expected/log.txt new file mode 100644 index 0000000..584302a --- /dev/null +++ b/jenner-check/t005_montecarlo_integration/expected/log.txt @@ -0,0 +1,46 @@ + +NOTE: Option OBS changed to 100. +NOTE: DATA generate + + +NOTE: Wrote generate (100 rows, 2 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: DATA sums + + +NOTE: Read 100 rows from generate. +NOTE: Wrote sums (100 rows, 2 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: DATA cumulative_sums + + +NOTE: Read 100 rows from sums. +NOTE: Wrote cumulative_sums (100 rows, 3 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: DATA cumulative + + +NOTE: Read 100 rows from cumulative_sums. +NOTE: Wrote cumulative (100 rows, 3 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: Option TITLE changed to a). +NOTE: ODS Graphics settings reset to defaults. +NOTE: ODS Graphics image-name index reset. +NOTE: ODS Graphics is ON (width=6.4in, height=4.8in, format=SVG). +NOTE: PROC SGPLOT data=WORK.CUMULATIVE + +NOTE: PROC SGPLOT output written to: ./ods_output/sgplot.svg +NOTE: ODS Graphics settings reset to defaults. +NOTE: ODS Graphics image-name index reset. +NOTE: ODS Graphics is ON (width=640px, height=480px, format=SVG). +NOTE: PROC PRINT data=cumulative + +NOTE: PROC PRINT completed: 5 observations printed, 3 variables diff --git a/jenner-check/t005_montecarlo_integration/expected/output.txt b/jenner-check/t005_montecarlo_integration/expected/output.txt new file mode 100644 index 0000000..a01b65e --- /dev/null +++ b/jenner-check/t005_montecarlo_integration/expected/output.txt @@ -0,0 +1,11 @@ + a) + + Last few cumulative Monte Carlo estimates of f(x)=x integral + + Obs M mean_cum error_cum + 1 96 0.4556682187 0.0443317813 + 2 97 0.4607866959 0.0392133041 + 3 98 0.4656415198 0.0343584802 + 4 99 0.4647414422 0.0352585578 + 5 100 0.4681874082 0.0318125918 + diff --git a/jenner-check/t005_montecarlo_integration/meta.json b/jenner-check/t005_montecarlo_integration/meta.json new file mode 100644 index 0000000..9a69217 --- /dev/null +++ b/jenner-check/t005_montecarlo_integration/meta.json @@ -0,0 +1,7 @@ +{ + "bundle": "t005_montecarlo_integration", + "source_file": "DZ 10/divanovic_doris_dz10_zad1.sas", + "source_blob_sha": "46a531cc50618a7c67b1ab71efcb4e2c70a739f2", + "source_commit": "7ca6d5646283a3e35c3d4e645e4006a8ad0ecf39", + "notes": "Crude Monte Carlo integration of f(x)=x over [0,1] via uniform importance sampling (part a) of a 4-part script; only part a) is included in this bundle, parts b-d use the same technique with different sampling distributions). Tracks the running mean and absolute error against the closed-form answer 0.5 as replications accumulate, plotted with PROC SGPLOT. Edits applied: rep trimmed from 1000 to 200 for a lighter run (the observation cap further limits the captured cumulative dataset to 100 rows); a closing PROC PRINT of the last 5 cumulative rows was added so the bundle has a tabular snapshot alongside the graphics." +} diff --git a/jenner-check/t005_montecarlo_integration/script.sas b/jenner-check/t005_montecarlo_integration/script.sas new file mode 100644 index 0000000..89d07c4 --- /dev/null +++ b/jenner-check/t005_montecarlo_integration/script.sas @@ -0,0 +1,53 @@ +/* jenner-check: this bundle keeps part a) of the original script (the + crude Monte Carlo integration of f(x)=x over [0,1] by uniform + importance sampling) and trims rep from 1000 to 200 for a lighter + captured run; the sampling/estimation logic is unchanged. A closing + PROC PRINT of the last few cumulative rows was added so the bundle + has a tabular snapshot alongside the SGPLOT graphics. */ +%let seed = 575867; +%let rep = 200; +%let h = 0.5; + +/* **************** a) ********************* */ + +data generate; + call streaminit(&seed); + do M = 1 to &rep; + x = RAND("Uniform", 0, 1); + output; + end; +run; + +data sums; + set generate; + x = x / PDF('UNIFORM', x, 0,1); *f(x)=x; +run; + +data cumulative_sums; + set sums; + retain sum_cum; + if M = 1 then sum_cum = x; + else sum_cum = (sum_cum + x); +run; + +data cumulative; + set cumulative_sums; + mean_cum = sum_cum / M; + error_cum = abs(0.5 - mean_cum); + keep M mean_cum error_cum; +run; +title "a)"; +ods graphics / reset width=6.4in height=4.8in imagemap; + +proc sgplot data=WORK.CUMULATIVE; + vline M / response=mean_cum lineattrs=(pattern=mediumdash color=blue) + stat=mean; + xaxis label="Broj replikacija" values=(0 to 1000 by 100); + yaxis grid label="Kumulativni prosjek"; +run; + +ods graphics / reset; + +proc print data=cumulative(firstobs=96); + title "Last few cumulative Monte Carlo estimates of f(x)=x integral"; +run; diff --git a/jenner-check/t006_sampling_variability/autoexec.sas b/jenner-check/t006_sampling_variability/autoexec.sas new file mode 100644 index 0000000..719f93f --- /dev/null +++ b/jenner-check/t006_sampling_variability/autoexec.sas @@ -0,0 +1,2 @@ +/* cap input rows for the captured run */ +options obs=100; diff --git a/jenner-check/t006_sampling_variability/expected.json b/jenner-check/t006_sampling_variability/expected.json new file mode 100644 index 0000000..2538a49 --- /dev/null +++ b/jenner-check/t006_sampling_variability/expected.json @@ -0,0 +1,13 @@ +{ + "_captured_at": "2026-07-12T12:17:32Z", + "_captured_run_id": "r_065f2f710bea4b379438e08656aa15de", + "status": "ok", + "exit_code": 0, + "log_contains": [ + "NOTE: Output dataset OutData has 5 observations and 4 variables.", + "NOTE: PROC PRINT completed: 5 observations printed, 4 variables", + "NOTE: PROC SGPLOT output written to: ./ods_output/sgplot.svg" + ], + "log_does_not_contain": ["ERROR:", "[JENNER-ERROR"], + "diagnostics": {"parse_warnings": [], "runtime_warnings": []} +} diff --git a/jenner-check/t006_sampling_variability/expected/log.txt b/jenner-check/t006_sampling_variability/expected/log.txt new file mode 100644 index 0000000..c2c4856 --- /dev/null +++ b/jenner-check/t006_sampling_variability/expected/log.txt @@ -0,0 +1,21 @@ + +NOTE: Option OBS changed to 100. +NOTE: DATA SimUniSize + + +NOTE: Wrote SimUniSize (100 rows, 4 columns). +NOTE: DATA elapsed: + wall 0.00 seconds + cpu 0.00 seconds +NOTE: PROC MEANS +NOTE: Output dataset OutStats has 20 observations and 5 variables. +NOTE: PROC MEANS statement used. +NOTE: PROC MEANS +NOTE: Output dataset OutData has 5 observations and 4 variables. +NOTE: PROC MEANS statement used. +NOTE: PROC PRINT data=OutData + +NOTE: PROC PRINT completed: 5 observations printed, 4 variables +NOTE: PROC SGPLOT data=OutData + +NOTE: PROC SGPLOT output written to: ./ods_output/sgplot.svg diff --git a/jenner-check/t006_sampling_variability/expected/output.txt b/jenner-check/t006_sampling_variability/expected/output.txt new file mode 100644 index 0000000..c423b93 --- /dev/null +++ b/jenner-check/t006_sampling_variability/expected/output.txt @@ -0,0 +1,22 @@ + The MEANS Procedure + + Analysis Variable : SAMPLEMEAN + + N + N Obs Mean Std Dev + -------------------------------------- + 2 5 0.5429316 0.2784151 + 4 5 0.3765836 0.2017752 + 6 5 0.5146508 0.1615765 + 8 5 0.4722588 0.1030602 + -------------------------------------- + + + Obs N _TYPE_ _FREQ_ SAMPLESTD +----- - ------ ------ ------------ + 1 . 0 20 0.1918298864 + 2 2 1 5 0.2784150626 + 3 4 1 5 0.2017751676 + 4 6 1 5 0.1615764967 + 5 8 1 5 0.1030601691 + diff --git a/jenner-check/t006_sampling_variability/meta.json b/jenner-check/t006_sampling_variability/meta.json new file mode 100644 index 0000000..c4dba69 --- /dev/null +++ b/jenner-check/t006_sampling_variability/meta.json @@ -0,0 +1,7 @@ +{ + "bundle": "t006_sampling_variability", + "source_file": "DZ 3/divanovic_doris_dz3_zad4.sas", + "source_blob_sha": "e52960ad4d3863c071d960638fa366aacc68a6b3", + "source_commit": "7ca6d5646283a3e35c3d4e645e4006a8ad0ecf39", + "notes": "Simulation study of how sampling variability of the sample mean shrinks as sample size N grows: generates NumSamples independent uniform(0,1) samples at each of several sizes N, computes each sample's mean via PROC MEANS BY N SampleID, then looks at the standard deviation of those means across N via PROC MEANS CLASS N, plotted with PROC SGPLOT. Edits applied: NumSamples trimmed from 1000 to 5 and the N sweep trimmed from 10..200 by 10 to 2..8 by 2, so the generated dataset fits under this run's observation cap without truncating mid-size; a PROC PRINT of the summary table was added." +} diff --git a/jenner-check/t006_sampling_variability/script.sas b/jenner-check/t006_sampling_variability/script.sas new file mode 100644 index 0000000..594d33c --- /dev/null +++ b/jenner-check/t006_sampling_variability/script.sas @@ -0,0 +1,41 @@ +/* jenner-check: NumSamples trimmed from 1000 to 5, and the sample-size + sweep trimmed from N=10..200 by 10 (20 sizes) to N=2..8 by 2 (4 + sizes), so the generated dataset (5*(2+4+6+8)=100 rows) fits under + this run's observation cap without truncating mid-size. The + simulation technique -- generate NumSamples uniform samples at each + size N, compute each sample's mean, then look at how the standard + deviation of those sample means shrinks as N grows -- is unchanged. */ +%let NumSamples = 5; +%let seed = 123; + +data SimUniSize; + call streaminit(&seed); + do N = 2 to 8 by 2; + do SampleID = 1 to &NumSamples; + do i = 1 to N; + x = rand("Uniform"); + output; + end; + end; + end; +run; + +proc means data = SimUniSize noprint; + by N SampleID; + var x; +output out = OutStats mean = SampleMean; +run; + +proc means data = OutStats Mean Std; + class N; + var SampleMean; +output out = OutData std = SampleStd; +run; + +proc print data = OutData; +run; + +proc sgplot data = OutData; + scatter x = N y = SampleStd; +run; +/* ==> kako se povećava veličina uzorka, greška se smanjuje. */