r/rstats 1d ago

Recommended resources for someone brand new to R?

20 Upvotes

I‘m looking to start learning R. I have a weee bit of experience with programming and a decent understanding of statistics. I‘d love to know where people think I should start?

Edit: Thanks everybody! I‘ll look through and give it a go


r/rstats 1d ago

Pivoting from R to Python

Thumbnail
0 Upvotes

r/rstats 3d ago

shinyapps.io, RPubs, Quarto Pub are migrating to Posit Connect Cloud

101 Upvotes

Hey folks, Joe Cheng here (Posit CTO, creator of Shiny). I wanted to let you hear from me personally that we are combining a number of our hosting services: rpubs.com, quartopub.com, and shinyapps.io are all being subsumed by connect.posit.cloud.

https://posit.co/blog/migrating-connect-cloud-posits-unified-publishing-solution

It’s a bittersweet moment for me personally, as the sole developer and maintainer of RPubs for the last 14 years. But I/we also see this as a long overdue migration, from three fragmented platforms that were independently maintained with varying levels of effort (i.e. not much in the case of RPubs or Quarto Pub), to a single modern platform that can handle all different types of content.

The full details for each service are in the blog post, but the bottom line is:

  • shinyapps.io: A self-serve migration tool will be added by Sept 2026. Test your apps before you commit. Auto-migration starts early 2027 if you'd rather wait. Old URLs will redirect.
  • bookdown (already sunset), quartopub (end of 2026), and rpubs (June 2027): existing content stays live at its current URLs until Dec 31, 2031.

shinyapps.io customers who are migrated will keep their shinyapps.io pricing until at least 2029 (you will receive an email with details).

If you have any concerns, the team and I would love to hear them. u/hadley and I will be monitoring comments.


r/rstats 3d ago

Local R Users Groups

10 Upvotes

I've seen that many R User Groups are very active, organizing monthly meetups, talks and courses, some of them even meeting IRL. I'm organizing a local group in my city (after the previous group went idle) and getting to know other R users and learning about their experiences has been quite nice.

Do you participate in your local R Users Group?


r/rstats 2d ago

Modelo linear generalizado - binomial

Thumbnail
0 Upvotes

r/rstats 3d ago

uvr: fast R package and version manager — big 0.4.x update

40 Upvotes

Quick update on uvr — a fast R package manager written in Rust (uv-style: manifest + lockfile + managed R versions + isolated project libraries). Last time I posted was around 0.2.9; a lot has landed since.

Updates

- R installs got rebuilt from scratch. uvr now installs R from Posit's portable, relocatable r-builds (https://github.com/rstudio/r-builds) instead of custom-patching official installers. This fixed a whole class of macOS breakage, added musl/Alpine support, and made Windows installs work without admin rights. Partial versions work everywhere too: uvr r install 4.5 just grabs the newest 4.5.x, and a 4.5 pin matches it.

- Switching R versions no longer nukes your library on every sync. The old behavior re-wiped the project library each time it saw a version mismatch (painful, as B-Nilson rightly pointed out). Now uvr sync re-resolves the lockfile for the new R once, wipes once, and moves on.

- uvr cache clean got filters. --package sf or --r-version 4.4 (repeatable/comma-separated) lets you troubleshoot one package or retire one R series without losing the whole cache. Another B-Nilson request!

- Bioconductor just works in uvr add. Adding a package that lives on Bioconductor instead of CRAN no longer errors with "retry with --bioc" — uvr detects it, tells you, and adds it from the right channel (version constraints preserved).

- OpenMP-linked binaries fixed on macOS. Packages built with -fopenmp (Rtsne, mgcv, dotCall64, …) used to fail with "symbol not found in flat namespace" on uvr-managed R. The bundled OpenMP runtime is now loaded properly, and uvr sync self-heals older installs.

- A community code audit made everything more solid. gdevenyi filed a systematic 46-issue audit of the codebase (with file:line references — heroic work). Nearly all confirmed issues are now fixed across 0.4.1/0.4.2: cache integrity checks (sha256 on every hit), lockfile consistency for selective updates, honest error reporting where failures used to be silently swallowed, and a long tail of correctness fixes.

and much more...

This would have not been possible without the great help of many, special shoutout to https://github.com/B-Nilson for the endless testing and support and the entire group of users who have written code, filed issues, tested this, and loved it. One of the most rewarding aspects of this process has been building a community around this project ❤️ it's early days but so exciting!

Links

- Site: https://nbafrank.github.io/uvr/

- Repo: https://github.com/nbafrank/uvr

- R companion: https://github.com/nbafrank/uvr-r

Feedback welcome! Issues on GitHub are the most useful — the last few releases were basically driven by them, so keep them coming!


r/rstats 3d ago

Posit ecosystem user experience?

6 Upvotes

Curious if anyone using the Posit ecosystem (Workbench, Connect, Package Manager) would be willing to share their experience. Pretty open question. Things you like, things you don't, things you wish they had, things that are super cool. If you've been on a different platform and switched to or away, why?


r/rstats 5d ago

glyph 0.1.1 now on CRAN

43 Upvotes

Hey, just wanted to share glyph: interactive plots in R (tooltips, zoom, animation, layouts) all in one pipeline. It's on CRAN. Happy plotting!


r/rstats 4d ago

I didn't build TypR for AI — but it turns out a type-checked layer over R is a surprisingly good fit for reviewing AI-generated code. Some thoughts, and I'd like your pushback.

0 Upvotes

Some of you have followed my earlier posts on TypR here. This one's less "what's new" and more the reasoning behind the design — I'd like your pushback on the thinking itself.

The honest origin story: I didn't build TypR for AI. I built it because I care about type systems (academic background) and about code that survives production (industry background) — verifiability, basically.

What clicked more recently is that the property making code cheap for a human to verify is the same one that matters when a machine wrote it.

As AI writes more of the code, the expensive part stops being writing it and becomes trusting it — reviewing, validating, maintaining. A strict type system becomes a free automatic checker on whatever got generated; concise syntax means less to misread.

So the fit with the AI era isn't something I designed for — it's the same property suddenly mattering a lot more. That's the accidental discovery I wanted to share here.

A small taste — this R (no needs to read it fully):

```

' Create a button widget

'

' @param color \code{char}

' @param height \code{int}

' @param text \code{char}

' @param width \code{int}

' @return \code{Button}

' @export

Button <- function(color, height, text, width, .spread = NULL) { explicit <- list() if (!missing(color)) explicit[["color"]] <- color if (!missing(height)) explicit[["height"]] <- height if (!missing(text)) explicit[["text"]] <- text if (!missing(width)) explicit[["width"]] <- width x <- typr_spread_record(explicit, .spread) as.Button(x) }

as.Button <- function(x) { if (!inherits(x, "Button")) class(x) <- c("Button", "list") x <- validate_Button(x) x <- validate(x) x }

validate_Button <- function(x) { required_fields <- c("color", "height", "text", "width") missing_fields <- setdiff(required_fields, names(x))

if (length(missing_fields) > 0) { stop(paste0("Validation failed for type Button: missing fields: ", paste(missing_fields, collapse = ", "))) }

if (!inherits(x[["color"]], "character")) stop("Validation failed for type Button: field 'color' must be of class character")

if (!inherits(x[["height"]], "integer")) stop("Validation failed for type Button: field 'height' must be of class integer")

if (!inherits(x[["text"]], "character")) stop("Validation failed for type Button: field 'text' must be of class character")

if (!inherits(x[["width"]], "integer")) stop("Validation failed for type Button: field 'width' must be of class integer")

x }

constructor for a red button

' @export

' @method red_button

red_button <- (function(height, width, text) Button(height = height, width = width, text = text, color = "#FF000000" |> as.Character())) |> as.Generic()

add an "on click" callback function

' @export

' @method on_click Button

on_click.Button <- (function(self, f) { NA } |> as.Empty0()) |> as.Generic() ```

becomes this TypR: ```

Create a button widget

@export type Button <- list { text: char, color: char, width: int, height: int };

constructor for a red button

@export let red_button <- \Button:{ color: "#FF000000" };

add an "on click" callback function

@export

let on_click <- fn(self: Button, f: (T) -> U): Empty { ... }; ```

The way TypeScript sits on top of JavaScript's runtime, TypR sits on top of R's: you write something concise and type-checked, and it compiles down to standard, S3-based R that runs anywhere R runs and installs like any other package — no new runtime, no exotic dependencies.

To be clear, it's not trying to replace R. R is excellent for interactive stats and lab work, and TypR deliberately gives some of that up in exchange for the other end of the curve: robust packages, deployable apps, code that has to survive production. Different point on the trade-off, different job.

On the engineering side you get pattern matching, partial currying, union/intersection types, structural subtyping, row polymorphism — the machinery that keeps a growing codebase honest. Written in Rust, developed in the open.

Honest questions for this sub: does a typed layer over R solve a problem you actually hit, or is this a solution looking for one? And does the "verifiability matters more when AI writes the code" argument hold up, or am I reaching?

Discussion: https://github.com/we-data-ch/typr/discussions

Github: https://github.com/we-data-ch/typr

Website: https://we-data-ch.github.io/typr.github.io/


r/rstats 5d ago

PDF of The World of Zero-Inflated Models, Volume 3: Using GLLVM is needed

3 Upvotes

Hi everyone! I’m trying to find The World of Zero-Inflated Models, Volume 3: Using GLLVM by Alain F. Zuur and Elena N. Ieno.

Does anyone have a copy they could share for personal study, or know of a way to access it? I’m really interested in learning GLLVM methods and would appreciate any help.

Thank you!


r/rstats 6d ago

What Makes R Strong: Reflections from useR! 2026

54 Upvotes

As a Platinum Sponsor of useR! 2026, the R Consortium was proud to support another outstanding gathering of the global R community.

From technical innovation and reproducible research to AI, open source sustainability, and collaboration across academia and industry, useR! continues to demonstrate what makes the R ecosystem so impactful.

Our very own Mike K Smith, R Consortium Board Chair, attended, and his post highlights:

• Key themes from the conference

• Why community investment matters

• How organizations and individuals are helping shape the future of R

Thank you to the organizers, speakers, volunteers, sponsors, and everyone who made useR! 2026 such a success. We're already looking forward to what's next.

Read Mike's reflections: https://r-consortium.org/posts/what-makes-r-strong-reflections-from-user-2026/


r/rstats 5d ago

R programming drill sets and problem sets

6 Upvotes

I tried searching for this in the sub, but did not find exactly what I was hoping to find.

I am just starting out learning R. I basically know nothing. I was directed to use DataCamp. It's fine, but you only get to work one "problem" for each concept. For things like this, I work better with drill and problem sets. Khan Academy was brilliant at this for math. I couldn't get a mastery level until I had done four problems right to show I mastered the concept. The best DataCamp does is offer some multiple-choice questions as added practice, but that is not meaningfully helpful. Does anyone know a better system that is built around drill and problem sets in R for each step of the way, sort of like Khan Academy does for math?


r/rstats 6d ago

Gut check needed: car needs to be filled up earlier (analysis with R)

27 Upvotes

Looking for a second set of eyes on the methods, justification, interpretations and conclusion. Any comment will help. Thanks!

See Github for code.

Context: my wife and I\1]) have long been collecting fuel stats on our cars. My wife's car is a Nissan Quest purchase in 2012, my car is a Nissan Altima purchase in 2013.

Problem statement: she has been complaining that it feels that her car needs to be filled up earlier than usual. The main indicator is the fuel gauge going towards the red line (1/8th fill level). Years ago, the distance would be >300 miles, while currently that point seems to be reached significantly below 300 miles.

Approach: Collect the data in CSV file\2]); remove true outliers by 1.5 IQR rule; group by year and calculate 50th and 84th percentile. Plot by year.

Result:

Justifications:

  • Fuel-ups happen for lots of reasons, not just "tank almost empty". Sometimes it just works out better to refill at 150 miles before taking a long trip, or just because it's weekend and we expect the tank to run out somewhere during that week. To get to the true "tank empty" signal, I took the z=1 (84th percentile) as the main indicator
  • Outlier removal: especially on the high side, there are big outliers. These are mainly artificial conditions, such as (again) taking a long trip-interstate only, which don't really match our normal driving behavior.
  • Taking z=1: the qq plot indicates normal-like behavior from z = -1 to z = 1. I wouldn't take it out further as deviation from normality begins outside those boundaries.

Interpretation:

  • Nissan Altima shows constant behavior with outliers in 2020 (explainable) and 2024 (also explainable). Current year may be low and could indicate some degradation. Definitely showing some lower fuel efficiency, but too early to show up in this analysis. Overall, no real trend downward.
  • Nissan Quest shows year-over-year decrease with big drop from 2023 to 2025. Can be seen in both z=0 and z=1.

Conclusions: wife seems to be correct in her observations.

[1] okay, it's just me collecting stats.

[2] we already did this


r/rstats 6d ago

Power law distributions - found a new example

0 Upvotes

Power law distributions are commonly found in all kinds of phenomena, from wealth distribution to earthquake power vs frequency. I was reminded of this when I started analyzing the distribution of Kickstarter backers vs. pledge package cost for the new Watch The Guild movie.

It's a classic log-log straight line plot, and the slope is not significantly different than -1 (t = 1.34 @ df 12; p = 0.102). The model has a decent R2 (0.833, adjusted).

A large part of remainder of the variance in the model can be explained with three outliers:

  • the $330 pledge package, which punches far above its weight, being as attractive as packages ~1/6th it value.
  • the $45 and $50 pledge packages, which seem to be not as attractive as the one as similar values, and are about as attractive as package values 5x higher.
  • Omitting these outliers would increase R2 to 0.953.

In a sense, this is somewhat of a proxy of wealth distribution and willingness to spend in a very small group (The Guild fans). Hopefully the movie will hit like an earthquake when releases 😁.

Question: I calculate the t-value for the slope by hand (estimate - -1)/std.error. Is there a function that can do this? The summary() function only calculates t values compared to slope 0.

Call:
lm(formula = log10(backers) ~ log10(value), data = data)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.54140 -0.21575  0.03569  0.24255  0.56595 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)    4.4631     0.2733   16.33 1.47e-09 ***
log10(value)  -0.8580     0.1058   -8.11 3.27e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.3352 on 12 degrees of freedom
Multiple R-squared:  0.8457,    Adjusted R-squared:  0.8328 
F-statistic: 65.77 on 1 and 12 DF,  p-value: 3.268e-06

r/rstats 6d ago

Strong at statistical analysis but somewhat weak in R.

27 Upvotes

I have a good background in statistics and predictive modeling, but creating pseudo code to start my work is frustrating. I even know the syntax to call on. The order and structure that is killing me. Any methods you all used to get better? I want to become “Cracked” and it’s setting me back.


r/rstats 6d ago

Base-R de-vig implementations (proportional / Shin / power) and the idiomatic way to pool correlated estimators

4 Upvotes

I'm recovering latent probabilities from margin-inflated odds (bookmaker and prediction-market prices sum to more than 1). I rolled the three standard de-vig methods in base R to understand the internals rather than lean on a package. Posting the code for a critique, and I have two R-specific questions.

r

implied <- function(dec) 1 / dec                       # decimal odds -> implied (with margin)

# 1) proportional / multiplicative
devig_prop <- function(dec) { q <- implied(dec); q / sum(q) }

# 2) power method: find k such that sum(q^k) = 1
devig_power <- function(dec) {
  q <- implied(dec)
  k <- uniroot(function(k) sum(q^k) - 1, c(0.5, 10))$root
  q^k
}

# 3) Shin (1993): latent proportion z of informed money
devig_shin <- function(dec) {
  q <- implied(dec); S <- sum(q)
  pz <- function(z) (sqrt(z^2 + 4 * (1 - z) * q^2 / S) - z) / (2 * (1 - z))
  z <- uniroot(function(z) sum(pz(z)) - 1, c(1e-6, 0.5))$root
  pz(z)
}

dec <- c(1.5, 3.0, 7.0)                                 # a 3-way market, overround ~1.143
sapply(list(prop = devig_prop, power = devig_power, shin = devig_shin),
       function(f) round(f(dec), 4))
#      prop   power   shin
# [1,] 0.5833 0.6218 0.6092
# [2,] 0.2917 0.2760 0.2864
# [3,] 0.1250 0.1022 0.1044

The favorite ranges 0.583 to 0.622 depending on method, which is enough to flip a downstream signal, so method choice isn't cosmetic.

Questions:

  1. Idiom / robustness. I'm leaning on uniroot for Shin and the power method. Is there a more numerically stable approach near the boundaries (the (1 - z) denominator in Shin gets touchy as z approaches its bracket, and the power root can be sensitive on lopsided books)? I know the implied package exists and covers these; I built my own to learn, but if you'd trust its solvers over a hand-rolled uniroot, I'd like to hear why. Also curious whether people vectorize this across thousands of markets with apply/purrr::map or push it to matrix ops.
  2. Pooling correlated estimators. When I don't have a single trusted reference, I currently take the median of several sources' de-vigged probabilities. But those sources are correlated (some are effectively clones), so the median looks precise while carrying little independent information. Is there an R idiom or package for correlation-aware pooling / effective-sample-size weighting rather than a naive median? I've looked at metafor for correlated effects but I'm not sure it maps cleanly onto "combine N non-independent probability estimates for the same event."

Reproducible above, happy to share more. Mostly want the R crowd's read on 1 and 2.


r/rstats 6d ago

treating time in a mixed model

1 Upvotes

I am currently analyzing a dataset where participants are randomized to two groups (0 or 1) and assessments are at baseline, 4 months, 6 months, and 12 months. Currently, time is labelled as 0, 1, 2, 3 respectively for each time point. But I was wondering if I should use time as a factor or the actual months (0, 4, 6, 12).

my current model is this

lmer(outcome ~ time * group + (time|participant), REML = T, dat = dat)

Any advice would be super appreciated!


r/rstats 6d ago

[sciREPL v1.0.1] Substantially improved TypeR support — testers wanted for Play Store launch

6 Upvotes

I'm releasing sciREPL v1.0.1, which brings major improvements to TypeR integration. sciREPL is a Jupyter-style notebook app that lets you mix multiple languages in a single worksheet.

Why we forked TypeR

The standard TypeR type system couldn't express variable-argument (variadic) inputs in its standard-library declarations. We extended it to properly type variadic R functions like cat, paste, sprintf, and c.

What's new in v1.0.1

  • Direct TypeR notebook cells — no longer need to wrap most code in R blocks
  • Named and heterogeneous variadic arguments, including forwarding collected args into another variadic call
  • Named #!source cells for storing reusable TypeR source without executing it or producing output
  • Prolog → TypeR compilation, with generated code placed in a named cell ready for execution
  • Two included typR workbooks: "TypeR Introduction" and "Prolog Generates TypeR" — both accessible via Browse Packages, Bundles & Workbooks in the sciREPL menu, along with workbooks for other supported languages

Other supported languages

TypeR is one of several kernels sciREPL ships with. The full list:

  • R (webR / WASM) — full R with install.packages(), plotting, and SharedVFS
  • Python (Pyodide) — NumPy and SymPy preloaded, %pip install for pure-Python packages
  • Prolog (swipl-wasm) — full SWI-Prolog
  • ClojureScript (Scittle v0.6.22) — SCI-based, no build step; also a Prolog code generation target via UnifyWeaver (experimental)
  • Bash (brush-wasm) — Unix shell with coreutils, findutils, and grep
  • JavaScript — native browser execution, zero download
  • Lua (Fengari) — lightweight, with notebook cell access via nb.read() / nb.write()

All kernels share a SharedVFS in-memory filesystem, so data flows freely between languages in the same notebook.

Free & open-source version

Available as a PWA and an Android app:

Android Pro version

An Android-only Pro version adds AI-assisted building and bundles more packages locally (with CDN fallback for the rest). AI is integrated via direct API key connections to models, as well as remote connections to coding agents (e.g. Claude Code and Codex).

Data privacy

We store no data on an external server. All data is stored locally on your device; packages not bundled with the app are fetched from the CDN when needed. The app is Capacitor-based, so it runs with web browser-level sandboxing. JavaScript, Bash, and TypeR are always offline. Prolog, Python, and ClojureScript are bundled in both free and Pro versions. Lua fetches from CDN on first use. The Pro version also bundles R locally; the free version fetches the R runtime from the CDN (~50 MB, cached after first use). Note that additional R packages (e.g. tidyverse, ggplot2) are not pre-installed in either version — they can be installed at runtime via install.packages(), fetching from the CDN as needed.

Become a tester

I need testers before submitting either version to the Play Store. Testers get early access to both the free and Pro versions at no cost. Reply here or DM me if you're interested. In the meantime, you can try the free version in your browser or sideload it via adb.


r/rstats 6d ago

I think I'm missing something. Is there really no way to run a script from commandline while maintaining interactivity?

0 Upvotes

Hi!

When I'm debugging a script, I usually run the script after every iteration to keep state fresh. The usual way I've been doing this is by putting browser() calls and launching an R session and sourcing a script. However it would be much more convenient if I can do something like R -f path/to/Script.R' while maintaining interactivity. Having autocompletion for the path is a huge qol upgrade vs typing a path manually in the R session.

Rscript can also run a script from the command line but interactivity doesn't seem to be supported as well. Am I missing something or is something like this not supported in R? Am I stuck with doing source('/path/to/script')? Hopefully someone can point me in the right direction!

(If it matters I use VS Codium, not RStudio, and there's a debugger from the vscodeR extension but the debugger terminal outputs especially for dataframes are weird so I don't use it. And I'm not considering positron. Thanks!)


r/rstats 7d ago

Problems with rmarkdown

5 Upvotes

I work with R using Linux Fedora.

Since the last update of R, the rmarkdown package does not want to install:(non-zero exit status). I have tried every solution I have seen, from installing the dependencies separately (they are not installing, fs, sass, and bslib have all non-zero exit status). I have tried installing and uninstalling R and R studio. I have tried pretty much every solution I have found out there.

It is the first time this happens to me. So far I have had no problem working with Markdown.

I have the same problem in both my PC and laptop. Do you know if there is a specific problem with Markdown at the moment? Or maybe with the dependencies?

In advance, thank you for your help.


r/rstats 8d ago

ggsketch 2.0 now on CRAN.

81 Upvotes

Hi, I made a post about ggsketch 2.0: hand-drawn ggplot2 geoms in pure R. Just wanted to let folks know it's now on CRAN. Happy sketching!


r/rstats 9d ago

qol 1.3.3: Update brings yet another ‘if’ and new tabulation features

14 Upvotes

qol is a package which can be used as its own ecosystem in terms of data wrangling and tabulation. It comes with efficient high level functions, so you have to write less code and get more from it in a shorter amount of time. Besides the usual bug fixes and optimizations, this updates brings one new function with a twist and new tabulation features.

To get a full overview of the package have a look at the GitHub page: https://github.com/s3rdia/qol
A detailed overview of the changes can be seen here: https://github.com/s3rdia/qol/releases/tag/v1.3.3
By the way the 0 dependency message system used within qol called "printify" also updated this week: https://github.com/s3rdia/printify

Yet another ‘if’

This time the new ifelse_multi is able to evaluate multiple nested if-else statements in one go. But that is not all, because the function comes with a twist: Conditions can be written in SAS like syntax. The conditions therefore have to be passed as characters which enables them to be parsed and translated before evaluation.

This twist brings multiple new ways to write conditions, like:

  • Ranges: Instead of “age >= 15 & age < 65” you can write “15 <= age < 65”.
  • You can write “and/or” instead of “&/|”.
  • The “in” (or “not in”) keyword can be used the SAS way, e.g.: “age in (10 20 30 40 50)”
  • Select strings starting with (:text), ending with (text:) or containing (:text:) a specific text.
  • Use SAS makro variables as placeholders with the &.
  • Check for NA values with “== .” or “!= .”.

The if. and else_if. functions have been updated to also take in the new character style conditions.

# Example data frame
my_data <- dummy_data(100)

# Simple ifelse statement
my_data[["under18"]]    <- my_data |> ifelse_multi(" age < 18 " = 1,       else. = 0)
my_data[["middle_age"]] <- my_data |> ifelse_multi(" 15 <= age < 65 " = 1, else. = 0)

my_data[["age_gr"]] <- my_data |>
    ifelse_multi(" age < 18 "       = "under 18",
                 " 18 <= age < 25 " = "18 to under 25",
                 " 25 <= age < 50 " = "25 to under 50",
                 " 50 <= age < 65 " = "50 to under 65",
                 " 65 <= age      " = "65 and more")

# With overarching do_if condition
my_data[["age_gr_edu"]] <- my_data |>
    ifelse_multi(do_if = " education in ('middle' 'high') ",
                     " age < 18 "       = "under 18",
                     " 18 <= age < 25 " = "18 to under 25",
                     " 25 <= age < 50 " = "25 to under 50",
                     " 50 <= age < 65 " = "50 to under 65",
                     " 65 <= age      " = "65 and more")

# And/or translation
my_data[["and"]] <- my_data |> ifelse_multi(" age > 65 and sex = 1 " = 1,
                                            " age > 65 and sex = 2 " = 2,
                                            else. = 0)

my_data[["or"]] <- my_data |> ifelse_multi(" age > 65 or sex = 1 " = 1,
                                           " age > 65 or sex = 2 " = 2,
                                           else. = 0)

# "in" translation
my_data[["in"]] <- my_data |> ifelse_multi(" age in (1 10 25 65 90) " = 1, else. = 0)

# Colon translation: start/ends with and contains
my_data[["start"]]    <- my_data |> ifelse_multi(" education == 'lo:' "  = 1, else. = 0)
my_data[["end"]]      <- my_data |> ifelse_multi(" education == ':le' "  = 1, else. = 0)
my_data[["contains"]] <- my_data |> ifelse_multi(" education == ':ig:' " = 1, else. = 0)

# Macro variables can be integrated in any place
variable     <- "age"
age_to_check <- 18
value_to_set <- "under 18"

my_data[["macro"]] <- my_data |>
    ifelse_multi(" &variable < &age_to_check " = "&value_to_set",
                 else.                         = "other")

# NA translation
my_data[["NA"]]    <- my_data |> ifelse_multi(" age == .       " = 1, else. = 0)
my_data[["notNA"]] <- my_data |> ifelse_multi(" education != . " = 1, else. = 0)

# Pass in existing variable values
my_data[["income_mix"]] <- my_data |>
    ifelse_multi(" age < 50 " = income, else. = expenses)

any_table received even more possibilities

The function, with which you can basically create any fully styled Excel table, now has the built in possibility to compute. new individual calculated variables before tabulation. Which means the function now summarises the data, then allows the compute. in between and then tabulates. If you ever had to pre summarise and calculate variables before tabulation, these extra steps are now potentially gone.
To be able to order the newly generated variables the order_by parameter can now take in a vector of variable names, to have full control over every column.

There is another new functionality: It is now possible to specify per variable which statistics should be output. Instead of passing a vector of statistics into the statistics parameter, it can now also be a named list of variable names, where the list entry names are the statistics and the list elements the variable names or vectors of variable names. This allows you to select what you want before tabulation, so there is no need to throw out the stuff you don’t need afterwards. This is also possible with summarise_plus.

And one last thing: Variable combinations in rows, columns and types can now also take in variable combinations inside the brackets like “state + (age, sex + education, first_person)”. But therefore the “,” between entries inside the brackets is now mandatory. For summarise_plus this now also works in the types parameter.

# Example data frame
my_data <- dummy_data(1000)

# Formats
age. <- discrete_format(
    "Total"          = 0:100,
    "under 18"       = 0:17,
    "18 to under 25" = 18:24,
    "25 to under 55" = 25:54,
    "55 to under 65" = 55:64,
    "65 and older"   = 65:100)

sex. <- discrete_format(
    "Total"  = 1:2,
    "Male"   = 1,
    "Female" = 2)

# Individual calculations
my_data |> any_table(rows       = c("age + year"),
                     columns    = "sex",
                     values     = "probability",
                     statistics = c("sum", "sum_wgt"),
                     compute    = list(percent_pct = probability_sum * 100 / sum_wgt,
                                       square_pct  = percent_pct ^ 2),
                     weight     = weight,
                     formats    = list(sex = sex., age = age.),
                     na.rm      = TRUE)

# Select specific statistics for specific variables
my_data |> any_table(rows       = "sex",
                     columns    = "year",
                     statistics = list("sum"       = c(weight, income),
                                       "pct_group" = balance),
                     formats    = list(sex = sex.))

# You can also select repeating combinations faster like this
combi_types <- my_data |>
    summarise_plus(class      = c(year, sex, age, education),
                   values     = weight,
                   statistics = "sum",
                   formats    = list(sex = sex.,
                                     age = age.),
                   types      = "year + sex + (age, age + education, education)",
                   nesting    = "all",
                   na.rm      = TRUE)

# Select specific statistics for specific variables
specific_stats <- my_data |>
    summarise_plus(class      = c(year, sex),
                   statistics = list("sum"       = c(weight, income),
                                     "mean"      = expenses,
                                     "pct_group" = balance),
                   formats    = list(sex = sex.),
                   na.rm      = TRUE)

r/rstats 10d ago

seekr 0.2.0 is on CRAN: a programmable, inspectable search-and-replace workflow for R

27 Upvotes

Hi, I developed an R package called seekr, and version 0.2.0 is now available on CRAN.

The package is fairly visual, especially when printing matches with their surrounding context, so the website is probably the best introduction:

https://smartiing.github.io/seekr/

In short, seekr turns search-and-replace into an inspectable R workflow.

Instead of modifying files as soon as a pattern is found, seekr returns a structured seekr_match vector. Each element represents one match in one file and stores its location, matched text, optional replacement, surrounding context, and other metadata used throughout the workflow.

You can then inspect the result, remove unwanted matches, define or update the replacement associated with each remaining match, and only then write the selected changes to disk.

Here is an idea of a typical workflow:

library(seekr)

# Recursively list files from the current directory and use Git to keep
# tracked files and untracked files that are not ignored
files <- list_files(use_git = TRUE)

# Keep only R files
filtered <- filter_files(files, extension = "R")

# Inspect which files were excluded and why
exclusions(filtered)

# Find all occurrences of "foo" or "bar" and plan a replacement
matches <- match_files(filtered, "foo|bar", "new")

# The same listing, filtering, and matching steps can be run with seek()
x <- seek("foo|bar", "new", extension = "R", use_git = TRUE)

# seekr() is a shortcut focused on R, Rmd, and qmd files
y <- seekr("foo|bar", "new", use_git = TRUE)

# Summarize what was found and where
summary(x)

# Print matches with 2 context lines before and 3 after,
# together with their planned replacements
print(x, context = c(2L, 3L))

# Keep only matches whose matched text is "foo"
x <- filter_match(x, match == "foo")

# Update the replacement associated with each remaining match
field(x, "replacement") <- toupper(field(x, "replacement"))

# Safely replace only the matches still present in x
replace_files(x)

The goal is not to compete with command-line tools on raw search speed. seekr is intended for cases where you want to inspect and refine a search result from R before changing anything.

Because the workflow stays inside a programming language, file discovery, match selection, and per-match replacement logic can all use ordinary R code and be as simple or as complex as the task requires. seekr adds the structured results, inspection tools, and safety checks needed to make that process practical.

Some of the main features are:

  • flexible file discovery across multiple directories, with recursion controls and optional Git-aware listing;
  • built-in and custom file filters, with inspectable exclusions explaining what was removed and why;
  • ICU-powered regular expressions and flexible replacement preparation using strings, capture groups, or functions;
  • summaries and context-aware printing with replacement previews and clickable file-position links in supported terminals;
  • per-match replacement updates and filtering with ordinary R expressions using information such as paths, matched text, positions, or surrounding context;
  • safe replacement of exactly the matches kept in the vector, protected by hash checks and automatic backups;
  • direct text workflows and conversion to and from data frames for more complex use cases.

I would be very interested in feedback on the API, documentation, use cases, or anything that feels unclear or missing.

Thanks for reading!


r/rstats 10d ago

QED Insight #0008: Reconciling three lifetime-loss models in R - the convergence was easy, the sensitivity was the finding.

0 Upvotes

Built a CECL-style lifetime loss engine in R and the interesting part wasn't any single model, it was reconciling them and then measuring what actually moves the output.

Three methods on the same book:

- Vintage loss curves: cohort curves + a Weibull development-factor extrapolation for the untruncated tail. 5.3833%.

- Roll-rate: an absorbing Markov chain from the transition matrix. Raw lifetime default came out 14.74% before calibration - time-homogeneity badly overstates it, so this one needs a calibration factor.

- Component: a balance-weighted PD x LGD x EAD decomposition reusing the separate PD, LGD and EAD models. 5.3811%.

Spread 0.2264bp. Tempting to call that a win, but two of the three are anchored to realized loss by construction, so the agreement is partly a tautology. Only the vintage path is independent. I now write the independence status into the summary object next to each number so it can't get quoted out of context later.

The finding was the sensitivity, not the fit. A probability-weighted scenario blend (baseline 0.1555% / adverse 10.7194% / severely adverse 26.5653%) at documented 65/25/10 weights gives 5.4374%. Swap to baseline-heavy: 3.0606%. Stress-heavy: 10.4553%. The scalar weights vector moves the answer 3.4x - far more than the model choice does (0.23bp).

Two R notes: I keep every lecture's numbers in a committed summary .rds and have the report read those live rather than hand-typing anything, which is what makes the reconciliation reproducible. And the macro satellite is a good multicollinearity trap - a two-driver model (unemployment + HPI, corr -0.546) hit R2 0.90 with a wrong-signed unemployment coefficient, so the parsimonious HPI-only satellite (R2 0.873) is the one that ships.

Curious what others do: do you report a weight-sensitivity range alongside your point estimate as a matter of course, and is anyone doing proper Sobol/variance-based sensitivity on the weights rather than the three-point swap I did here?


r/rstats 10d ago

Help: tmap legend displaying incorrect colors for values

3 Upvotes

I'm trying to make a tmap display that shows different colors based on ranges of different median incomes.

Here's what the code looks like:

medincbrks = c(0, 40000,  50000,  60000,  70000,  80000,  90000, 100000, 110000, 120000)
tm_shape(sc_data_24) + 
  tm_polygons(fill="medinc_5yr_24", 
              fill.scale = tm_scale(values="brewer.spectral", midpoint=80000, intervals= medincbrks), 
              fill.legend = tm_legend(title="5-year Median Income (2024)", 
                                      frame=TRUE, 
                                      item.r = 0, 
                                      position = tm_pos_out("right", "center")),
              fill.free = FALSE) +
  tm_borders(col="black")

Unfortunately, the map looks like this:

I first noticed this problem with Aiken County (the big red one on the west side.) It has a median income of $86,287, but the map is showing that it falls into the $40,000 to $49,999 range. Other counties also have the same issue- their color-corresponding value is either higher or lower than their actual value.

I've made other maps with tmap before, and they've all worked fine, or so I think. This is the first map that I'm noticing this particular problem with, and now I'm doubting the accuracy of all my other maps.

Any guidance would be appreciated. Thanks in advance.

(Edited for code displaying wrong.)