Gilles Colling @gilles-colling.bsky.social · 07/04/2026Third use-after-free in a month. Only triggers above 4 million points — never shows up in testing. Rewrite the backend in Rust. The borrow checker catches out-of-bounds errors at COMPILE time that would've been segfaults weeks later. 1/3 110
Gilles Colling @gilles-colling.bsky.social · 07/04/2026Puzzle: how many copies does x$a[1] <- 99L trigger on a data.frame? TWO. One value changed, two full copies made. A data.frame is a list of column vectors. The list and each column are separate objects with separate reference counts. Modify one column and R copies the column AND the container. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 07/04/20265 km apart: 73% species overlap. 50 km: 31%. 200 km: 8%. Fitting the decay curve takes five minutes. But the residuals are where it gets interesting — pairs at the same distance that are consistently more or less similar than predicted. 1/3 100
Gilles Colling @gilles-colling.bsky.social · 07/04/2026Relative abundance went up 30%. Which part moved — focal species increased, or everything else decreased? A ratio collapses two biological processes into one number. Can't tell which drove it. numdenom fits numerator and denominator as a joint model with shared random effects. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 07/04/2026Square grid cells at the equator are 2x the area of cells at 60°N. Bigger cells capture more observations — and if a spatial process spans more than a few degrees of latitude, that area distortion might mess up ALL the downstream statistics. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 03/04/2026New alien plant species per decade in Austria: 1950s → 14 2010s → 47 No plateau. The rate itself is accelerating. #biodiversity 010
Gilles Colling @gilles-colling.bsky.social · 03/04/2026VIF says drop temperature. But there are 23 valid subsets of uncorrelated variables — and temperature survives in 19 of them. VIF picked one subset and hid the other 22. corrselect enumerates ALL of them. You choose. #rstats 021
Gilles Colling @gilles-colling.bsky.social · 03/04/2026left_join() with relationship = "many-to-many" suppresses the warning. But a warning you can silence isn't a contract. join_strict() makes cardinality explicit: 1:1, 1:many, many:many. State what you expect. Get a hard error when it's violated. #rstats 000
Gilles Colling @gilles-colling.bsky.social · 03/04/202680% of eBird checklists in Austria are within 500m of a parking lot. That species gradient you're mapping? It's a gradient of where people stop their cars. #ecology 020
Gilles Colling @gilles-colling.bsky.social · 03/04/2026Relative abundance went up 30%. Sounds like good news — until you realize the focal species stayed flat and everything else crashed. A ratio can't tell you which side moved. numdenom models numerator and denominator jointly so you actually know. #rstats 000
Gilles Colling @gilles-colling.bsky.social · 30/03/2026100 species observed. Is that 100 out of 100 or 100 out of 300? Extrapolation curves flatten when sampling is near-complete — each additional sample adds almost nothing. 1/3 110
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Package depends on X. X depends on Y. Y breaks on the new R release. Your package breaks and you didn't change a single line. Every Imports entry is a bet that the maintainer will keep up with R-core's release cycle. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Run richness at 1 km, 10 km, 100 km. Hotspots invert. The pattern you find depends on the grain you chose. If your conclusions flip at a different resolution, the resolution IS the finding. #ecology 000
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Without `force(x)`: "before" prints, then nothing — `x` is never evaluated because nothing used it. Arguments in R aren't evaluated when the function is CALLED. They're evaluated when they're first ACCESSED. This is lazy evaluation. 1/3 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Point falls on the boundary between two polygons. Which one gets it? Depends on the algorithm, the floating-point precision, and sometimes the order of polygons in the file. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026R's own functions: `is.numeric`, `as.data.frame`, `read.csv`, `seq.int`. Base R chose dot.case decades ago. Writing `is_numeric` next to `is.numeric` in the same pipe creates visual noise. Match the ecosystem. #rstats 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026The species list says 120 species COULD live here based on habitat and climate. You observed 74. That gap — 46 species absent from suitable habitat — is dark diversity. High observed richness with low dark diversity: the community is saturated. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026`g()` sees `x` because R uses LEXICAL scoping — functions look up variables in the environment where they were DEFINED, not where they were called. `g` was defined inside `f`, so `g`'s parent environment is `f`'s execution environment. This is why closures work. 1/3 110
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Variables as nodes. Draw edges where correlation exceeds the threshold. Take the complement graph — flip every edge. Now every maximal clique in the complement is a valid subset where NO pair exceeds the threshold. Bron-Kerbosch (1973) finds ALL maximal cliques. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Zero rows. One row. NA in the key column. Duplicate keys. Column name with a space. Factor with unused levels. These aren't exotic inputs. They show up in real pipelines every week. If your function hasn't been tested against each one, it works on demos, not data. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/202680% of eBird checklists in Austria are within 500m of a parking lot. The species gradient tracks access, not ecology. Any spatial model trained on this data learns where birders walk, not where birds live. #ecology 000
Gilles Colling @gilles-colling.bsky.social · 30/03/2026This isn't a string. It's a LANGUAGE OBJECT with class `formula`. R parsed it into an abstract syntax tree — `~` is the root, left branch is `y`, right branch is `+` with children `x1` and `x2`. Every modelling function in R walks this tree. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Mahalanobis distance on a full community matrix. Species that co-occur in weird combinations light up as multivariate outliers — BORG flags the records, you decide what they mean. One function, no training step. #rstats 000
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Profile first. The inner loop runs 10 million times. Vectorized R: 4.2 seconds. Rewrite JUST that loop in C++: 0.08 seconds. Rcpp isn't a style choice. It's a scalpel. Find the bottleneck with `profvis`, confirm it's a tight loop that can't vectorize, rewrite THAT function. 1/2 200
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Overall trend: species A is declining. Split by habitat: increasing in forests, increasing in grasslands, increasing in wetlands. Every habitat shows a gain. The aggregate shows a loss. How? Sampling effort shifted toward habitats where the species was ALREADY rare. 1/3 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Six equidistant neighbors. Every neighbor shares an edge, not a corner. No orientation bias — north-south and east-west distances are equal. Squares can't do any of this. Hexagons are the spatial unit your analysis deserves. #rstats 000
Gilles Colling @gilles-colling.bsky.social · 30/03/202642 million rows. You need 3 columns and records from 2024. Loading the whole file to filter afterward wastes RAM on data you're about to throw away. Filter runs inside the parser. Non-matching rows are skipped as raw bytes — never tokenized, never allocated. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026y doesn't copy anything. R just points two names at the same memory. The moment you write `y[1] <- 99L`, R copies the ENTIRE vector — 40 MB — to keep x unchanged. This is copy-on-modify. It's why "R is slow" complaints often come from code that triggers copies inside loops. Modify in place? 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Add sites from south to north. Richness climbs fast — looks like a latitudinal gradient. Randomize the order. Same sites, same species, slower climb. The curve shape reflects the ORDER you added sites, not the diversity of the landscape. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 30/03/2026Pipe a grouped tibble into a function that doesn't expect groups. No error. Silent wrong results. keyed tags the key columns as metadata — downstream functions can CHECK whether the data is grouped by what they expect before running. #rstats 000
Gilles Colling @gilles-colling.bsky.social · 29/03/2026You deleted every variable in your workspace. The function you defined five minutes ago still returns 10. f <- local({ x <- 10; function() x }) rm(list = ls()) f() # 10 That x lives inside the function itself. 1/4 100
Gilles Colling @gilles-colling.bsky.social · 28/03/2026First CRAN submission to acceptance: 11 days, three rounds. The first rejection was one missing tag in one documentation file. #rstats 130
Gilles Colling @gilles-colling.bsky.social · 28/03/2026ggguides — p + legend_inside("topright") replaces 5 lines of legend.position / legend.justification / element_rect boilerplate, returns a theme object, chains with + #rstats 161
Gilles Colling @gilles-colling.bsky.social · 28/03/2026left_join in R is silent when keys aren't unique. It just multiplies rows. The output has more rows than the input and nothing tells you. #rstats 320
Gilles Colling @gilles-colling.bsky.social · 26/03/2026Thinking about building occupancy models with INLA. Detection/occupancy with spatial random effects. Has anyone done this? I haven't found many examples beyond the standard textbook cases and I'm curious what the pain points are before I start. #rstats 001
Gilles Colling @gilles-colling.bsky.social · 25/03/20261654, Paris. A gambler rolls a single die four times and bets he'll get at least one six. He wins more often than he loses. He rolls two dice 24 times and bets on at least one double six. He loses more often than he wins. Both bets have the same expected number of successes. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 25/03/2026Late March and the Alps still have a meter of snow above 1800m. Spent the afternoon up there. Just cold air and the sound your boots make in spring snow, that wet crunch. If you're anywhere near mountains this time of year, go. They're still winter up top and spring at the base. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 25/03/2026Started learning Affinity Designer last week for graphical abstracts. I'm a second year PhD student and I have no idea what I'm doing in a design tool. But it's fun. One-time purchase, no subscription, no telemetry. 1/2 100
Gilles Colling @gilles-colling.bsky.social · 24/03/2026In the last year of my PhD I split my time roughly 80/20. Eighty percent on the research, twenty percent on side projects. Some weeks more like 60/40. The side projects taught me C++, package development, spatial algorithms, things that had nothing to do with my thesis on paper. 1/3 120
Gilles Colling @gilles-colling.bsky.social · 24/03/2026I wrote about this with interactive simulations where you can watch the paradox form and dissolve as you change the data. gillescolling.com/blog/simpsons-par… 000
Gilles Colling @gilles-colling.bsky.social · 24/03/2026Simpson's Paradox: Hospital A wins every surgery. Hospital B wins overall. Both are true. Once you see it you'll find it everywhere. 000
Gilles Colling @gilles-colling.bsky.social · 24/03/2026I wrote about this with interactive simulations where you can watch the paradox form and dissolve as you change the data. gillescolling.com/blog/simpsons-par… 000
Gilles Colling @gilles-colling.bsky.social · 24/03/2026Simpson's Paradox: Hospital A wins every surgery. Hospital B wins overall. Both are true. Once you see it you'll find it everywhere. 000
Gilles Colling @gilles-colling.bsky.social · 24/03/2026I wrote about this with interactive simulations where you can watch the paradox form and dissolve as you change the data. gillescolling.com/blog/simpsons-par… 000
Gilles Colling @gilles-colling.bsky.social · 24/03/2026Simpson's Paradox: Hospital A wins every surgery. Hospital B wins overall. Both are true. Once you see it you'll find it everywhere. 000
Gilles Colling @gilles-colling.bsky.social · 24/03/2026You left_join two tables. One has 10,000 rows. The result has 14,000. Where did 4,000 extra rows come from? Duplicate keys in the right table. Every duplicate multiplies your rows silently. No warning, no error, just a data frame that's 40% larger than it should be. 1/4 200
Gilles Colling @gilles-colling.bsky.social · 24/03/2026200 treated patients. 800 controls. Five covariates to match on. How do you pair them? Propensity scores collapse those five dimensions into one number and match on that. Clean and well-understood. But you can also solve the pairing directly in the original covariate space. 1/4 111
Gilles Colling @gilles-colling.bsky.social · 24/03/2026I fell in love with the R ecosystem during my PhD. Thousands of open source packages, all free. I never had to depend on Microsoft or a university license to do science. Now I write ecology packages and put them on CRAN. Independent tools for independent science. #rstats 0221
Reposted by Gilles CollingJournal of Open Source Software @joss-openjournals.bsky.social · 25/02/2026Just published in JOSS: 'corrselect: Fast and flexible predictor pruning for data analysis and modeling' doi.org/10.21105/joss.09539 011