Sign in

Constantin Ahlmann-Eltze

@const-ae.bsky.social
2.1K followers 699 following 70 posts

Scientist at Isomorphic Labs. Previously at UCL and EMBL working with Wolfgang Huber. Biology, ML, Python, R, cancer immunology

PostsRepliesMedia
Constantin Ahlmann-Eltze @const-ae.bsky.social · 22/03/2026
Thrilled to join Isomorphic Labs—time to build better ML models of cells and create the drugs of the future!! 🧬🧫🧑‍💻🥳
Picture of myself in front of the Iso logo
2241
Constantin Ahlmann-Eltze @const-ae.bsky.social · 04/08/2025
Our paper benchmarking foundation models for perturbation effect prediction is finally published 🎉🥳🎉 www.nature.com/articles/s41... We show that none of the available* models outperform simple linear baselines. Since the original preprint, we added more methods, metrics, and prettier figures! 🧵
Beeswarm plot of the prediction error across different methods of double perturbations showing that all methods (scGPT, scFoundation, UCE, scBERT, Geneformer, GEARS, and CPA) perform worse than the additive baseline.Line plot of the true positive rate against the false discovery proportion showing that none of the methods is better at finding non additive interactions than simply predicting no change.
212557
Constantin Ahlmann-Eltze @const-ae.bsky.social · 19/02/2025
I have found a solution that works by squashing the quasi-quotation and explicitly setting the environment. I still wonder if there is a better solution or if someone (maybe @lionelhenry.bsky.social? 😅) would have to write a `quo_squash` function that retains the environments. :) #Rstats
wrap_in_tibble <- function(...){
  dots <- rlang::enquos(...)
  # Let's assume I can be sure that the environment is the same across dots
  eval_in_mtcars(tibble::tibble(!!! dots), env = rlang::quo_get_env(dots[[1]]))
}

eval_in_mtcars <- function(expr, env){
  quo <- rlang::enquo(expr)
  quo <- rlang::new_quosure(rlang::quo_squash(quo), env)
  rlang::eval_tidy(quo, data = mtcars[1:3,])
}

wrap_in_tibble(mpg * 3)
#> # A tibble: 3 × 1
#>   `mpg * 3`
#>       <dbl>
#> 1      63  
#> 2      63  
#> 3      68.4
010
Constantin Ahlmann-Eltze @const-ae.bsky.social · 19/02/2025
In this example `enexprs()` would work, but it breaks if I call `wrap_in_tibble` from a function. (When calling fnc(), it should evaluate the quasi-quotation with a=700.)
  wrap_in_tibble2 <- function(...){
    dots <- rlang::enexprs(...)
    eval_in_mtcars(tibble::tibble(!!! dots))
  }
  fnc <- function(){
    a <- 700
    wrap_in_tibble2(mpg * a, cyl + 3)
  }
  a <- 0
  fnc()
  wrap_in_tibble2(mpg * a, cyl + 3)
100
Constantin Ahlmann-Eltze @const-ae.bsky.social · 19/02/2025
Is there some clever syntax to control when a quasi-quotation is evaluated so that the wrap_in_tibble function works? 🧐 (Context in stackoverflow.com/questions/79...) #Rstats #rlang
wrap_in_df <- function(...){
  dots <- rlang::enquos(...)
  eval_in_mtcars(data.frame(!!! dots))
}
wrap_in_tibble <- function(...){
  dots <- rlang::enquos(...)
  eval_in_mtcars(tibble::tibble(!!! dots))
}
eval_in_mtcars <- function(expr){
  quo <- rlang::enquo(expr)
  rlang::eval_tidy(quo, data = mtcars[1:3,])
}

wrap_in_df(mpg * 2, cyl + 3)
#>   X.mpg...2 X.cyl...3
#> 1      42.0         9
#> 2      42.0         9
#> 3      45.6         7
wrap_in_tibble(mpg * 2, cyl + 3)
#> Error: object 'mpg' not found
221
Constantin Ahlmann-Eltze @const-ae.bsky.social · 12/02/2025
I just uploaded the first version of {{exactplot}} to github.com/const-ae/exa... 🎉 It produces figures with consistent font size, Latex labels, and millimeter-perfect layouting. It's an alternative to patchwork with less elegant syntax but much more flexibility. #rstats
Plot produced with exactplotxp_compose_plots(
  xp_text("Welcome to \\texttt{exactplot} with \\LaTeX{} support", x = 1, y = 2, 
          fontsize = xp$fontsize_large, fontface = "bold"),
  xp_text("A) Flipper length vs.\\ beak size", x = 1, y = 8),
  xp_plot(scatter_plot, x = 0, y = 12, width = 80, height = 40),
  
  xp_text("B) Beak sizes", x = 84, y = 8),
  xp_plot(beak_lengths, x = 82, y = 12, width = 58, height = 40),
  xp_text(density_formula, x = 99, y = 12, fontsize = xp$fontsize_small),
  
  width = 140, height = 52,
  keep_tex_file = FALSE, filename = "example.pdf"
)
610423
Constantin Ahlmann-Eltze @const-ae.bsky.social · 12/02/2025
I compiled my workflow for producing pretty figures into a small package: github.com/const-ae/exa... My trick is to use `tikzDevice` and do all the rendering with Luatex. I can position every panel and label with millimeter precision and still modify the output with Illustrator.
Plot produced with exactplot that contains Latex labels and has embedded fonts.
010
Constantin Ahlmann-Eltze @const-ae.bsky.social · 03/01/2025
Finally, we demonstrate LEMUR's usefulness on - a treatment vs. ctrl dataset from glioblastoma, - Zebrafish developmental time course, and - a spatially resolved Alzheimer dataset where we find intriguing DE patterns.
Screenshot of the glioblastoma figureScreenshot of the Zebrafish figureScreenshot of the Alzheimer plaques figure
130
Constantin Ahlmann-Eltze @const-ae.bsky.social · 03/01/2025
To solve this, we find for each gene the neighborhood of cells that are - close together in cell-type space, and - have maximal differential expression. This aggregation improves DE detection as we can optimally adjust our "cluster" resolution for each gene.
Example of the differential expression neighborhood inference
120
Constantin Ahlmann-Eltze @const-ae.bsky.social · 03/01/2025
But more intuitively, think of this approach as optimal transport where you match the __space__ occupied by cells instead of matching individual cells between conditions. On top, this means you don't need to worry about abundance changes within cell types.
Subspace alignment schematic
150
Constantin Ahlmann-Eltze @const-ae.bsky.social · 03/01/2025
At its core, LEMUR is a matrix factorization with a _twist_! Instead of finding a single subspace R to approximate the data, I find one subspace per condition R(x). Inspired by generalized linear models (GLMs), I implement this as solving a regression on subspaces.
Schematic of a simplified model with 2 genes where we fit 1D subspaces.
130
Constantin Ahlmann-Eltze @const-ae.bsky.social · 03/01/2025
LEMUR is directly useable for your next single-cell differential expression project! R package: github.com/const-ae/lemur Python package: pylemur.readthedocs.io/en/latest/ Code from the paper: github.com/const-ae/lem...
R and Python code to call LEMUR
161
Constantin Ahlmann-Eltze @const-ae.bsky.social · 03/01/2025
After 4y in the making, I am super excited that my main PhD project is published 🎉🥳🎉🎉🥳 www.nature.com/articles/s41... LEMUR is a tool to analyze multi-condition single-cell data and model differential expression as a continuous function of the cell-state space. Some highlights⬇️
Overview of the LEMUR steps: (1) subspace alignment, (2) differential expression, (3) DE neighborhoods, (4) pseudobulking.
815435
Constantin Ahlmann-Eltze @const-ae.bsky.social · 20/09/2024
There's a lot of excitement about foundation models and their ability to learn biology 🧬💻 But current tools for perturbation prediction perform worse than simple linear models! We need more careful benchmarking to make progress. www.biorxiv.org/content/10.1...
Beeswarm plot of the double perturbation predictions. The Additive model outperforms GEARS, scGPT, and scFoundation by a large marginLine plot of the accuracy of the predictions that are most different from the additive model. The No Change model outperforms GEARS, scGPT, and scFoundation.Beeswarm plot of the single perturbation predictions. The linear model perform as good as GEARS, scGPT, and scFoundation.
22111
Constantin Ahlmann-Eltze @const-ae.bsky.social · 19/09/2024
Very excited to announce that I started a postdoc in James Reading's lab at UCL working on tumor immunology 🥳 So, if you are in London and want to meet for a coffee, hit me up :)
020
Constantin Ahlmann-Eltze @const-ae.bsky.social · 12/09/2024
Over on mastodon, @teunbrand.bsky.social presented his new package {{gguidance}} to customize your legends and axis in #ggplot: fosstodon.org/@teunbrand/1... Now you can, for example, easily distinguish groups of discrete labels without facetting! #rstats
Code screenshot of 

```
ggplot(palmerpenguins::penguins, 
       aes(x = interaction(island, species), y = bill_length_mm)) +
  geom_boxplot() + 
  ggh4x::scale_x_manual(values = c(1:3, 4.5, 6)) +
  guides(x = gguidance::guide_axis_nested(drop_zero = FALSE))
```

plus the outcome where the x-axis is grouped by species
35221
Constantin Ahlmann-Eltze @const-ae.bsky.social · 28/08/2024
I wrote a new ggplot2 extension to draw Bezier curves github.com/const-ae/ggb... 🎉 The curves are defined by a set of control points and the tangents at those points. This makes each parameter directly interpretable and easy to tweak! #rstats #ggplot2
An example plot of the new {{ggbezier}} package
070
Constantin Ahlmann-Eltze @const-ae.bsky.social · 04/07/2024
Over at Twitter, my colleague Paula has posted about our new R package {{fullRankMatrix}} available on CRAN twitter.com/PaulaH_W/sta... Check it out at github.com/Pweidemuelle... The package helps with design matrices that contain colinear columns. #rstats
Hex sticker for the fullRankMatrix R package
041
Constantin Ahlmann-Eltze @const-ae.bsky.social · 10/02/2024
We validated LEMUR's performance on a compendium of 13 single-cell datasets to demonstrate: * that linear methods are flexible enough to integrate multiple conditions, * LEMUR accurately predicts gene expression counterfactuals, * LEMUR's DE test controls the FDR!
Screenshots from figure 4Screenshots from figure 4Screenshots from figure 4
110
Constantin Ahlmann-Eltze @const-ae.bsky.social · 10/02/2024
We included two new datasets to show LEMUR's features: 1. Analysis of the interaction between latent cell state and developmental time in embryonic development. 2. Application to a spatial single-cell dataset measuring the influence of Alzheimer's plaque density on gene expression.
Screenshot from figure 3Screenshot from figure 3
100
Constantin Ahlmann-Eltze @const-ae.bsky.social · 10/02/2024
The LEMUR package is included in the latest Bioconductor release. So you can try it out immediately: bioconductor.org/packages/release/bioc/html/lemur.html
Screenshot of a simple example of LEMUR
130
Constantin Ahlmann-Eltze @const-ae.bsky.social · 10/02/2024
I just uploaded a new version of the LEMUR manuscript www.biorxiv.org/content/10.1... 🎉🥳 LEMUR disentangles observed and latent factors of multi-condition single-cell data and finds groups of cells with consistent differential expression for each gene. Details on the changes ⬇️
Step-by-step workflow of LEMURScreenshot of figure 2 from the manuscript showing the differential expression pattern of HIST3H2A
194
Constantin Ahlmann-Eltze @const-ae.bsky.social · 11/09/2023
I successfully defended my PhD on Friday 🥳 Thanks to everyone who joined me on this journey 🎓🧪
Happy me with a fun PhD hat just after my defense
1120