OSCR

An AI system to help scientists write expert-level empirical software.

Code ↔ Paper

11 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 11 matches
  1. [1] § Methods › scRNA-seq batch integration › Dataset ↔ implementation/notebooks/single_cell_batch_integration.ipynb, lines 407–426 · score 0.83 · c1 ae26872b1042, bd0c7 f7fd, ed, cells, batches
  2. [2] § Methods › Code mutation system ↔ implementation/futs.py, lines 15–23 · score 0.71 · upper confidence bound, AlphaZero, tree search, UCB, Flat, algorithm
  3. [3] § Methods › COVID-19 prediction › Dataset ↔ implementation/notebooks/flu-cornell-jhu-hierarchsir.ipynb, lines 1–148 · score 0.71 · hospital admissions, Forecast Hub, target variable, augment, jurisdiction, population
  4. [4] § Methods › scRNA-seq batch integration › Evaluating scRNA-seq batch integration on the OpenProblems.bio benchmark ↔ implementation/notebooks/single_cell_batch_integration.ipynb, lines 864–965 · score 0.64 · cellxgene_census, batch integration, uns, OpenProblems, bounds, score
  5. [5] § Public health: predicting COVID-19 hospitalizations ↔ implementation/notebooks/flu-cornell-jhu-hierarchsir.ipynb, lines 1–148 · score 0.62 · weighted interval score, Forecast Hub, week, territories, uncertainty, calibrated
  6. [6] § Methods › GIFT-Eval benchmark › Unified solution ↔ implementation/notebooks/single_cell_batch_integration.ipynb, lines 753–786 · score 0.60 · log1p, log transform, median
  7. [7] § Methods › Code mutation system ↔ implementation/futs.py, lines 96–155 · score 0.60 · search tree, backpropagation, visit, flat, PUCT, exploration
  8. [8] § Genomics: batch integration of scRNA-seq data ↔ implementation/notebooks/single_cell_batch_integration.ipynb, lines 864–965 · score 0.59 · single cell batch, batch integration, mouse, OpenProblems, prompt, metrics
  9. [9] § Methods › scRNA-seq batch integration › Replication of existing methods for batch integration ↔ scripts/validate_results.py, lines 31–71 · score 0.56 · fine tuned, zero shot, leakage, metrics, model
  10. [10] § Time-series forecasting: GIFT-Eval › Per-dataset solution ↔ implementation/playground_s3e1.py, lines 66–125 · score 0.53 · scikit learn, XGBoost, boosting, Python, models
  11. [11] § Methods › Code mutation system ↔ implementation/futs.py, lines 96–155 · score 0.53 · rank scores, tree search, UCB, flat, PUCT, exploration

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 968 lines · 33 KB · Apache-2.0 · 4 matches

  1. # %% [markdown]
  2. # # Overview
  3. #
  4. # As single-cell technologies advance, single-cell datasets are growing both in
  5. # size and complexity. Especially in consortia such as the Human Cell Atlas,
  6. # individual studies combine data from multiple labs, each sequencing multiple
  7. # individuals possibly with different technologies. This gives rise to complex
  8. # batch effects in the data that must be computationally removed to perform a
  9. # joint analysis.
  10. #
  11. # This task aims to develop a superhuman method for batch integration of
  12. # single-cell RNA-seq data which must remove the batch effect while not removing
  13. # relevant biological information. The input data is unnormalized raw gene
  14. # expression count data with multiple batches and consistent cell type labels. The
  15. # batch integrated output can be a low dimensional embedding of the data or a
  16. # feature matrix. The respective batch-integrated representation is then evaluated
  17. # using sets of metrics that capture how well batch effects are removed and
  18. # whether biological variance is conserved. There are over 200 methods developed
  19. # by humans for carrying this out and the goal is to develop methods that are
  20. # better than humans.
  21. # %% [markdown]
  22. # **Input Format:** The input data file (`input_adata`) is an `ad.AnnData` object
  23. # that includes the main data matrix in `.X` attribute (these are input raw gene
  24. # expression counts we want to transform). The data has already been subset to
  25. # 2000 highly variable genes.
  26. #
  27. # **Output Format:** The output data (`output_adata`) MUST be an `ad.AnnData`
  28. # object. The transformed dataset or embedding must be stored in `X_emb` key under
  29. # `obsm` annotation.
  30. #
  31. # ```
  32. # output_data = ad.AnnData(
  33. # obs=adata.obs,
  34. # var=adata.var,
  35. # obsm={
  36. # 'X_emb': # transformed dataset goes here.
  37. # },
  38. # )
  39. # ```
  40. #
  41. # Importantly, **to remove batch effects while conserving biological factors you
  42. # may experiment with data preprocessing (e.g. via scanpy) as well as modeling.**
  43. #
  44. # **Evaluation Harness and `eliminate_batch_effect_fn`:** You will implement a
  45. # function, `eliminate_batch_effect_fn`, to generate a transformed dataset that
  46. # represents the original data without batch effects. The evaluation harness will
  47. # evaluate via `score` function below whether the transformed datasets has
  48. # eliminated batch effects while preserving important biological features. In
  49. # particular, we would like to preserve cell type variation. The `score` function
  50. # implements various metrics used to measure how well you are doing on the task.
  51. # Your goal is to maximize the score from the `score` function below by writing
  52. # the best possible method/code for `eliminate_batch_effect_fn`.
  53. #
  54. # **1. Objective:**
  55. #
  56. # * The goal is to create a `eliminate_batch_effect_fn` that transforms the
  57. # dataset into a new dataset that not only eliminiates batch effects but
  58. # preserves biological information. We will evaluate the output using average
  59. # of the following specific metrics (after scaling):
  60. # * ASW Batch: Modified average silhouette width (ASW) of batch. The metric
  61. # is scaled so that 0 indicates suboptimal batch mixing and 1 indicates
  62. # optimal batch mixing.
  63. # * ASW Label: Average silhouette width of cell type labels. ASW is computed
  64. # on cell identity labels and scaled to a value between 0 (worst) and 1
  65. # (best).
  66. # * ARI: Adjusted Rand Index compares clustering overlap, correcting for
  67. # random labels and considering correct overlaps and disagreements. The
  68. # Adjusted Rand Index (ARI) compares the overlap of two clusterings; it
  69. # considers both correct clustering overlaps while also counting correct
  70. # disagreements between two clusterings. We compare the cell-type labels
  71. # with the NMI-optimized Louvain clustering computed on the integrated
  72. # dataset. The adjustment of the Rand index corrects for randomly correct
  73. # labels. An ARI of 0 or 1 corresponds to random labeling or a perfect
  74. # match, respectively. The score ranges between 0 and 1 with larger values
  75. # indicating better conservation of the data-driven cell identity
  76. # discovery after integration compared to annotated labels.
  77. # * NMI: The normalized mutual information is a version of the mutual
  78. # information corrected by the entropy of clustering and ground truth
  79. # labels (e.g. cell type). The score ranges between 0 and 1, with 0
  80. # representing no sharing and 1 representing perfect sharing of
  81. # information between clustering and annotated cell labels. NMI compares
  82. # overlap by scaling using mean entropy terms and optimizing Louvain
  83. # clustering to obtain the best match between clusters and labels.
  84. # Normalized Mutual Information (NMI) compares the overlap of two
  85. # clusterings. We use NMI to compare the cell-type labels with Louvain
  86. # clusters computed on the integrated dataset. The overlap was scaled
  87. # using the mean of the entropy terms for cell-type and cluster labels.
  88. # Thus, NMI scores of 0 or 1 correspond to uncorrelated clustering or a
  89. # perfect match, respectively. We performe optimized Louvain clustering
  90. # for this metric to obtain the best match between clusters and labels.
  91. # * Graph connectivity: Connectivity of the subgraph per cell type label.
  92. # The graph connectivity metric assesses whether the kNN graph
  93. # representation, G, of the integrated data directly connects all cells
  94. # with the same cell identity label. The resultant score has a range of
  95. # (0;1], where 1 indicates that all cells with the same cell identity are
  96. # connected in the integrated kNN graph, and the lowest possible score
  97. # indicates a graph where no cell is connected.
  98. # * Isolated labels ASW: Score how well isolated labels are distinguished
  99. # from all other labels using the average-width silhouette score. Isolated
  100. # cell labels are defined as the labels present in the least number of
  101. # batches in the integration task. The score evaluates how well these
  102. # isolated labels separate from other cell identities. The isolated label
  103. # ASW score is obtained by computing the ASW of isolated versus
  104. # non-isolated labels on the embedding and scaling this score to be
  105. # between 0 and 1. The final score for each metric version consists of the
  106. # mean isolated score of all isolated labels.
  107. # * Isolated labels F1: Evaluate how well isolated labels coincide with
  108. # clusters. Score how well isolated labels are distinguished from other
  109. # labels by data-driven clustering. The F1 score is used to evaluate
  110. # clustering with respect to the ground truth cell type labels. It returns
  111. # a value between 0 and 1, where 1 shows that all of the isolated label
  112. # cells and no others are captured in the cluster.
  113. # * kBET: kBET determines how well batches are mixed within a cell type. The
  114. # kBET algorithm determines whether the label composition of a k nearest
  115. # neighborhood of a cell is similar to the expected (global) label
  116. # composition. The test is repeated for a random subset of cells, and the
  117. # results are summarized as a rejection rate over all tested
  118. # neighborhoods. kBET score is scaled between 0 and 1 so that larger
  119. # scores are associated with better batch mixing.
  120. # * iLISI: Local inverse Simpson's Index for batch label. The metric
  121. # assesses whether clusters of cells in a single-cell RNA-seq dataset are
  122. # well-mixed across a categorical batch variable. The original iLISI score
  123. # ranges from 0 to the number of categories, with the latter indicating
  124. # good cell mixing. This is rescaled to a score between 0 and 1.
  125. # * cLISI: Local inverse Simpson's Index for cell type label. The metric
  126. # assesses whether clusters of cells in a single-cell RNA-seq dataset are
  127. # well-mixed across a categorical cell type variable. The original cLISI
  128. # score ranges from 0 to the number of categories, with the latter
  129. # indicating good cell mixing. This is rescaled to a score between 0
  130. # and 1.
  131. # * PCR: Principal component regression compares the explained variance by
  132. # batch before and after integration. The score ranges between 0 and 1.
  133. # The larger the score, the more different the variance contributions are
  134. # before and after integration.
  135. # * Cell cycle conservation score: Cell cycle conservation score based on
  136. # principle component regression on cell cycle gene scores. The cell-cycle
  137. # conservation score evaluates how well the cell-cycle effect can be
  138. # captured before and after integration. Values close to 0 indicate lower
  139. # conservation and 1 indicates complete conservation of the variance
  140. # explained by cell cycle. In other words, the variance remains unchanged
  141. # within each batch for complete conservation, while any deviation from
  142. # the preintegration variance contribution reduces the score.
  143. #
  144. # **2. Function Signature:**
  145. #
  146. # * Your `eliminate_batch_effect_fn` *must* adhere to this signature:
  147. #
  148. # ```python
  149. # def eliminate_batch_effect_fn(
  150. # adata: ad.AnnData,
  151. # config: dict[str, Any],
  152. # ) -> ad.AnnData:
  153. # # Your code here to return ad.AnnData without batch variation.
  154. # return output_data # ad.AnnData without batch variation
  155. # ```
  156. #
  157. # * `adata`: input ad.AnnData object containing raw gene expression counts in
  158. # `adata.X` field and batch labels in `adata.obs['batch']` field.
  159. # * `config`: Configuration parameters (dictionary for hyperparameters, etc.).
  160. # **Don't forget to specify parameters in this `config` dictionary.**
  161. #
  162. # * Your function **must return an ad.AnnData object** structured in the
  163. # following way:
  164. #
  165. # ```python
  166. # output_data = ad.AnnData(
  167. # obs=adata.obs,
  168. # var=adata.var,
  169. # obsm={
  170. # 'X_emb': # transformed dataset goes here.
  171. # },
  172. # )
  173. # ```
  174. #
  175. # **Your implementation should NOT use `cell_type` information in any way.**
  176. #
  177. # **3. Minimize use of specialized single-cell python packages.**
  178. #
  179. # There are many python packages used in single cell genomics. The only one that
  180. # we have installed in the coding environment is scanpy. Thus, instead of using
  181. # algorithms you might be tempted to use, please write your own native description
  182. # of these algorithms using scanpy, sklearn, numpy, scipy, tensorflow, torch, jax
  183. # or equivalent. There is much room to be creative in getting rid of batch
  184. # variation using purely native tools.
  185. #
  186. # **4. Data Set Size:**
  187. #
  188. # Please be aware that the datafile is large and if you use the wrong algorithm
  189. # you could OOM your sandbox or the algorithm could take a long time to run. As an
  190. # example, a typical matrix size is 329,762 cells × 2,000 genes.
  191. #
  192. # **5. Error Handling:**
  193. #
  194. # * The harness includes robust error handling. If your
  195. # `eliminate_batch_effect_fn` raises an exception, the harness will catch it,
  196. # log the traceback, assign a `worst_score`, and continue the evaluation. This
  197. # is important to ensure that a single error doesn't halt the entire process.
  198. # The `traceback` and any `stdout` and `stderr` output from your function are
  199. # captured and stored.
  200. # * Pay attention to the `first_traceback` in the output. This is the *first*
  201. # error that occurred across all the datasets.
  202. # * Print statements within your `eliminate_batch_effect_fn` will be captured in
  203. # the `stdout` and `stderr` streams. Use these judiciously for debugging.
  204. #
  205. # **6. Configuration:**
  206. #
  207. # * The `config` dictionary is passed to your `eliminate_batch_effect_fn`,
  208. # allowing you to parameterize your model. You should design your function to
  209. # utilize the values in the `config` dictionary.
  210. #
  211. # **7. Deliverable:**
  212. #
  213. # * Implement the `eliminate_batch_effect_fn` function to return an `ad.AnnData`
  214. # object in `X_emb` key under `obsm` annotation.
  215. # * Specify hyperparameters in the `config` dictionary. This dictionary will be
  216. # passed as an argument to your `eliminate_batch_effect_fn`.
  217. #
  218. # **8. Major advice:**
  219. #
  220. # * An expert has identified the following method as a promising candidate for
  221. # solving batch integration:
  222. # * Conditional Variational Autoencoder trained on gene expression,
  223. # conditioned on batch ID. Latent space explicitly split into biological
  224. # and batch components. Loss combines reconstruction error, KL divergence
  225. # for both latent components, and an adversarial loss (via gradient
  226. # reversal layer) penalizing batch information within the biological
  227. # component. Output is the batch-corrected biological latent embedding.
  228. # %%
  229. from IPython.display import clear_output
  230. # %%
  231. # Packages
  232. requirements = """
  233. anndata==0.11.3
  234. scanpy==1.10.4
  235. anndata2ri==1.3.2
  236. """
  237. with open('./requirements.in', 'w') as f:
  238. f.write(requirements)
  239. # %%
  240. !pip install -r ./requirements.in
  241. clear_output()
  242. # %%
  243. # Install scib from git to avoid GLIBC errors for lisi
  244. !git clone https://github.com/theislab/scib.git ./scib_source
  245. !cd ./scib_source && git checkout v1.1.7
  246. !pip install ./scib_source/
  247. !rm -rf ./scib_source
  248. clear_output()
  249. # %%
  250. %load_ext rpy2.ipython
  251. # %%
  252. %%R
  253. # Set global options to be non-interactive
  254. options(repos = c(CRAN = "https://cloud.r-project.org")) # Set a CRAN mirror
  255. options(pkgAsk = FALSE) # Attempt to suppress prompts globally
  256. # Install 'remotes' package
  257. # The 'ask = FALSE' argument is specific to install.packages
  258. install.packages('remotes', ask = FALSE)
  259. # Load the remotes package
  260. library(remotes)
  261. # Install 'kBET' from GitHub
  262. remotes::install_github('theislab/kBET', upgrade = "never")
  263. # %%
  264. import dataclasses
  265. import os
  266. import sys
  267. import warnings
  268. import anndata as ad
  269. from anndata.io import read_elem, sparse_dataset
  270. import h5py
  271. import numpy as np
  272. import pandas as pd
  273. import scanpy as sc
  274. import scib
  275. from scipy.sparse import csr_matrix
  276. INPUT_DIR = './datasets/'
  277. WORKING_DIR = './'
  278. INPUT_FILE_DIR = os.path.join(
  279. INPUT_DIR,
  280. 'single-cell-batch-integration',
  281. )
  282. INPUT_FILE_TRAIN = os.path.join(
  283. INPUT_FILE_DIR, 'ffdaa1f0-b1d1-4135-8774-9fed7bf039ba-train-dataset.h5ad'
  284. )
  285. INPUT_FILE_VAL = os.path.join(
  286. INPUT_FILE_DIR, 'ffdaa1f0-b1d1-4135-8774-9fed7bf039ba-val-dataset.h5ad'
  287. )
  288. INPUT_FILE_PRE_INTEGRATION_TRAIN = os.path.join(
  289. INPUT_FILE_DIR, 'ffdaa1f0-b1d1-4135-8774-9fed7bf039ba-train-solution.h5ad'
  290. )
  291. INPUT_FILE_PRE_INTEGRATION_VAL = os.path.join(
  292. INPUT_FILE_DIR, 'ffdaa1f0-b1d1-4135-8774-9fed7bf039ba-val-solution.h5ad'
  293. )
  294. INPUT_FILE_SCORE_BOUNDS_TRAIN = os.path.join(
  295. INPUT_FILE_DIR,
  296. 'score_train.median.bounds.csv',
  297. )
  298. INPUT_FILE_SCORE_BOUNDS_VAL = os.path.join(
  299. INPUT_FILE_DIR,
  300. 'score_val.median.bounds.csv',
  301. )
  302. # %%
  303. def open_h5(path: str) -> h5py.File:
  304. return h5py.File(path, 'r')
  305. # %%
  306. def read_anndata(
  307. file: str,
  308. backed: bool = False,
  309. **kwargs,
  310. ) -> ad.AnnData:
  311. """Read anndata file.
  312. :param file: path to anndata file in h5ad format.
  313. :param kwargs: AnnData parameter to group mapping.
  314. """
  315. f = open_h5(file)
  316. kwargs = {x: x for x in f} if not kwargs else kwargs
  317. if len(f.keys()) == 0:
  318. return ad.AnnData()
  319. # Check if keys are available.
  320. for name, slot in kwargs.items():
  321. if slot not in f:
  322. warnings.warn(
  323. f'Cannot find "{slot}" for AnnData parameter `{name}` from "{file}"'
  324. )
  325. adata = read_partial(f, backed=backed, **kwargs)
  326. if not backed:
  327. f.close()
  328. return adata
  329. def read_partial(
  330. group: h5py.Group,
  331. backed: bool = False,
  332. force_sparse_types: [str, list] = None,
  333. **kwargs,
  334. ) -> ad.AnnData:
  335. """Partially read h5py groups.
  336. :params group: file group :params force_sparse_types: encoding types to
  337. convert to sparse_dataset via csr_matrix. :params backed: If True, read sparse
  338. matrix as sparse_dataset. :params **kwargs: dict of slot_name: slot. By
  339. default use all available slots for the h5py file. :return: AnnData object.
  340. """
  341. if force_sparse_types is None:
  342. force_sparse_types = []
  343. elif isinstance(force_sparse_types, str):
  344. force_sparse_types = [force_sparse_types]
  345. slots = {}
  346. if backed:
  347. print('Read as backed sparse matrix...')
  348. for slot_name, slot in kwargs.items():
  349. print(f'Read slot "{slot}", store as "{slot_name}"...')
  350. if slot not in group:
  351. warnings.warn(f'Slot "{slot}" not found, skip...')
  352. slots[slot_name] = None
  353. else:
  354. elem = group[slot]
  355. iospec = ad._io.specs.get_spec(elem)
  356. if iospec.encoding_type in ['csr_matrix', 'csc_matrix'] and backed:
  357. slots[slot_name] = sparse_dataset(elem)
  358. elif iospec.encoding_type in force_sparse_types:
  359. slots[slot_name] = csr_matrix(read_elem(elem))
  360. if backed:
  361. slots[slot_name] = sparse_dataset(slots[slot_name])
  362. else:
  363. slots[slot_name] = read_elem(elem)
  364. return ad.AnnData(**slots)
  365. def dataframe_to_bounds_map(
  366. dataframe: pd.DataFrame,
  367. ) -> dict[tuple[str, str], tuple[float, float]]:
  368. indexed_df = dataframe.set_index(['dataset_id', 'metric_id'])
  369. bounds_map = {
  370. idx: (row['lower_bound'], row['upper_bound'])
  371. for idx, row in indexed_df.iterrows()
  372. }
  373. return bounds_map
  374. # %%
  375. # Input data.
  376. input_adata = read_anndata(
  377. INPUT_FILE_TRAIN,
  378. X='layers/counts',
  379. obs='obs',
  380. var='var',
  381. )
  382. print(input_adata)
  383. # %%
  384. # Data before integration, for computation of metrics.
  385. adata_pre_integration = read_anndata(
  386. INPUT_FILE_PRE_INTEGRATION_TRAIN,
  387. X='layers/normalized',
  388. obs='obs',
  389. var='var',
  390. uns='uns',
  391. )
  392. # Bounds for scaling of metrics, for computation of metrics.
  393. df_score_bounds_train = pd.read_csv(INPUT_FILE_SCORE_BOUNDS_TRAIN)
  394. bounds_map_train = dataframe_to_bounds_map(df_score_bounds_train)
  395. # We observed that isolated labels F1 is variable, thus we set [0,1] bounds
  396. bounds_map_train[
  397. ('364bd0c7-f7fd-48ed-99c1-ae26872b1042', 'isolated_label_f1')
  398. ] = [
  399. 0.0,
  400. 1.0,
  401. ]
  402. clear_output()
  403. # %%
  404. def subsample_adata(
  405. adata: ad.AnnData,
  406. target_size: int,
  407. batch_col='batch',
  408. celltype_col='cell_type',
  409. seed=42,
  410. ) -> ad.AnnData:
  411. """Subsamples ad.AnnData object while maintaining the distribution of batches and cell types.
  412. Args:
  413. adata (ad.AnnData): The AnnData object to subsample.
  414. target_size (int): The desired number of observations in the subsampled
  415. AnnData.
  416. batch_col (str): The column in adata.obs containing batch information.
  417. celltype_col (str): The column in adata.obs containing cell type
  418. information.
  419. seed (int): Random seed for reproducibility.
  420. Returns:
  421. ad.AnnData: The subsampled AnnData object.
  422. """
  423. np.random.seed(seed)
  424. obs = adata.obs[[batch_col, celltype_col]].copy()
  425. obs['index'] = obs.index
  426. # Calculate the desired proportions for each group
  427. group_counts = obs.groupby([batch_col, celltype_col], observed=True).size()
  428. total_counts = len(adata)
  429. group_proportions = group_counts / total_counts
  430. # Calculate the number of samples to take from each group
  431. group_sample_sizes = (group_proportions * target_size).astype(int)
  432. # Adjust sample sizes to match the target size as closely as possible
  433. remaining = target_size - group_sample_sizes.sum()
  434. if remaining != 0:
  435. sorted_groups = group_proportions.sort_values(ascending=False).index
  436. for group in sorted_groups:
  437. if remaining > 0:
  438. group_sample_sizes[group] += 1
  439. remaining -= 1
  440. elif remaining < 0:
  441. if group_sample_sizes[group] > 0:
  442. group_sample_sizes[group] -= 1
  443. remaining += 1
  444. if remaining == 0:
  445. break
  446. # Subsample each group
  447. sampled_indices = []
  448. for group, size in group_sample_sizes.items():
  449. group_indices = obs[
  450. (obs[batch_col] == group[0]) & (obs[celltype_col] == group[1])
  451. ]['index'].values
  452. sampled_indices.extend(
  453. np.random.choice(group_indices, size=size, replace=False)
  454. )
  455. # Create the subsampled AnnData object
  456. subsampled_adata = adata[sampled_indices, :].copy()
  457. return subsampled_adata
  458. # %%
  459. # Subsample data.
  460. input_adata = subsample_adata(input_adata, target_size=20000, seed=42)
  461. # Apply subsampling to pre-integrated data for metrics.
  462. adata_pre_integration = adata_pre_integration[input_adata.obs.index, :]
  463. print(input_adata)
  464. # %% [markdown]
  465. # # Evaluation
  466. # %%
  467. CONTROL_METHODS = [
  468. 'embed_cell_types',
  469. 'embed_cell_types_jittered',
  470. 'no_integration',
  471. 'no_integration_batch',
  472. 'shuffle_integration',
  473. 'shuffle_integration_by_batch',
  474. 'shuffle_integration_by_cell_type',
  475. ]
  476. DATASET_ID = adata_pre_integration.uns['dataset_id']
  477. METHOD_ID = 'scagent'
  478. @dataclasses.dataclass(frozen=True)
  479. class Result:
  480. dataset_id: str
  481. method_id: str
  482. metric_id: str
  483. value: float
  484. @property
  485. def is_control_method(self):
  486. return self.method_id in CONTROL_METHODS
  487. def scaled(self, bounds: dict[tuple[str, str], tuple[float, float]]):
  488. """Returns a new Result with the value scaled to the bounds."""
  489. lower_bound, upper_bound = bounds[(self.dataset_id, self.metric_id)]
  490. if (
  491. np.isnan(lower_bound)
  492. or np.isnan(upper_bound)
  493. or lower_bound >= upper_bound
  494. ):
  495. scaled_value = np.nan
  496. else:
  497. scaled_value = (self.value - lower_bound) / (upper_bound - lower_bound)
  498. return dataclasses.replace(self, value=scaled_value)
  499. def run_leiden_clustering(adata: ad.AnnData, resolution: float) -> ad.AnnData:
  500. # Based on
  501. # https://github.com/openproblems-bio/task_batch_integration/blob/main/src/data_processors/precompute_clustering_run/script.py
  502. from scanpy.tl import leiden
  503. key = f'leiden_{resolution}'
  504. kwargs = {'flavor': 'igraph', 'n_iterations': 2}
  505. leiden(
  506. adata,
  507. resolution=resolution,
  508. key_added=key,
  509. **kwargs,
  510. )
  511. return adata
  512. def score(
  513. adata: ad.AnnData,
  514. adata_pre_integration: ad.AnnData,
  515. bounds_map: dict[tuple[str, str], tuple[float, float]],
  516. dataset_id: str = DATASET_ID,
  517. method_id: str = METHOD_ID,
  518. ) -> float:
  519. # Suppress warnings just for scoring.
  520. with warnings.catch_warnings():
  521. warnings.filterwarnings('ignore', category=FutureWarning)
  522. warnings.filterwarnings('ignore', category=UserWarning)
  523. warnings.filterwarnings('ignore', category=DeprecationWarning)
  524. warnings.filterwarnings('ignore', category=RuntimeWarning)
  525. # Preprocess data - compute kNN.
  526. if 'X_emb' in adata.obsm and 'neighbors' not in adata.uns:
  527. sc.pp.neighbors(adata, use_rep='X_emb')
  528. # Preprocess data - compute clusterings at different resolutions.
  529. # Based on
  530. # https://github.com/openproblems-bio/task_batch_integration/blob/main/src/data_processors/process_integration/config.vsh.yaml
  531. resolutions = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
  532. for resolution in resolutions:
  533. adata = run_leiden_clustering(adata, resolution)
  534. # Based on
  535. # https://github.com/openproblems-bio/task_batch_integration/blob/main/src/metrics/clustering_overlap/script.py
  536. cluster_key = 'leiden'
  537. scib.metrics.clustering.cluster_optimal_resolution(
  538. adata=adata,
  539. label_key='cell_type',
  540. cluster_key=cluster_key,
  541. cluster_function=sc.tl.leiden,
  542. resolutions=resolutions,
  543. verbose=False,
  544. )
  545. # Compute metrics.
  546. asw_batch = scib.metrics.silhouette_batch(
  547. adata,
  548. batch_key='batch',
  549. label_key='cell_type',
  550. embed='X_emb',
  551. verbose=False,
  552. )
  553. print(f'ASW batch: {asw_batch}')
  554. asw_label = scib.metrics.silhouette(
  555. adata, label_key='cell_type', embed='X_emb'
  556. )
  557. print(f'ASW label: {asw_label}')
  558. ari_score = scib.metrics.ari(
  559. adata, cluster_key=cluster_key, label_key='cell_type'
  560. )
  561. print(f'ARI: {ari_score}')
  562. nmi_score = scib.metrics.nmi(
  563. adata, cluster_key=cluster_key, label_key='cell_type'
  564. )
  565. print(f'NMI: {nmi_score}')
  566. graph_connectivity = scib.metrics.graph_connectivity(
  567. adata, label_key='cell_type'
  568. )
  569. print(f'Graph connectivity: {graph_connectivity}')
  570. isolated_labels_asw = scib.metrics.isolated_labels_asw(
  571. adata,
  572. label_key='cell_type',
  573. batch_key='batch',
  574. embed='X_emb',
  575. iso_threshold=None,
  576. verbose=False,
  577. )
  578. print(f'Isolated labels ASW: {isolated_labels_asw}')
  579. isolated_labels_f1 = scib.metrics.isolated_labels_f1(
  580. adata,
  581. label_key='cell_type',
  582. batch_key='batch',
  583. cluster_key='leiden',
  584. resolutions=resolutions,
  585. embed=None,
  586. iso_threshold=None,
  587. verbose=False,
  588. )
  589. print(f'Isolated labels F1: {isolated_labels_f1}')
  590. # kBET
  591. kbet_score = scib.metrics.kBET(
  592. adata,
  593. batch_key='batch',
  594. label_key='cell_type',
  595. type_='embed',
  596. embed='X_emb',
  597. scaled=True,
  598. verbose=False,
  599. )
  600. print(f'kBET: {kbet_score}')
  601. # iLISI
  602. ilisi_scores = scib.metrics.lisi.lisi_graph_py(
  603. adata=adata,
  604. obs_key='batch',
  605. n_neighbors=90,
  606. perplexity=None,
  607. subsample=None,
  608. n_cores=1,
  609. verbose=False,
  610. )
  611. ilisi = np.nanmedian(ilisi_scores)
  612. ilisi = (ilisi - 1) / (adata.obs['batch'].nunique() - 1)
  613. print(f'iLISI: {ilisi}')
  614. # cLISI scores
  615. clisi_scores = scib.metrics.lisi.lisi_graph_py(
  616. adata=adata,
  617. obs_key='cell_type',
  618. n_neighbors=90,
  619. perplexity=None,
  620. subsample=None,
  621. n_cores=1,
  622. verbose=False,
  623. )
  624. clisi = np.nanmedian(clisi_scores)
  625. nlabs = adata.obs['cell_type'].nunique()
  626. clisi = (nlabs - clisi) / (nlabs - 1)
  627. print(f'cLISI: {clisi}')
  628. # PCR
  629. adata_integrated = adata.copy()
  630. adata_integrated.obs['batch'] = adata_pre_integration.obs['batch']
  631. pcr_score = scib.metrics.pcr_comparison(
  632. adata_pre_integration[:, adata_pre_integration.var['batch_hvg']],
  633. adata_integrated,
  634. embed='X_emb',
  635. covariate='batch',
  636. verbose=False,
  637. )
  638. print(f'PCR: {pcr_score}')
  639. # Cell cycle (compute this last since the var_names is updated)
  640. adata_pre_integration.var_names = adata_pre_integration.var['feature_name']
  641. adata_pre_integration.var_names = adata_pre_integration.var_names.astype(
  642. str
  643. )
  644. cell_cycle_score = scib.metrics.cell_cycle(
  645. adata_pre_integration,
  646. adata_integrated,
  647. batch_key='batch',
  648. embed='X_emb',
  649. organism=adata_pre_integration.uns['dataset_organism'],
  650. verbose=False,
  651. )
  652. print(f'Cell cycle conservation score: {cell_cycle_score}')
  653. scores = {
  654. 'asw_label': asw_label,
  655. 'asw_batch': asw_batch,
  656. 'ari': ari_score,
  657. 'nmi': nmi_score,
  658. 'graph_connectivity': graph_connectivity,
  659. 'isolated_label_asw': isolated_labels_asw,
  660. 'isolated_label_f1': isolated_labels_f1,
  661. 'kbet': kbet_score,
  662. 'ilisi': ilisi,
  663. 'clisi': clisi,
  664. 'pcr': pcr_score,
  665. 'cell_cycle_conservation': cell_cycle_score,
  666. }
  667. # Scale metrics using lower and upper bounds.
  668. scaled_scores = []
  669. for metric_id, value in scores.items():
  670. unscaled_score = Result(
  671. dataset_id=dataset_id,
  672. method_id=method_id,
  673. metric_id=metric_id,
  674. value=value,
  675. )
  676. scaled_score = unscaled_score.scaled(bounds_map)
  677. scaled_scores.append(scaled_score)
  678. df = pd.DataFrame(
  679. [dataclasses.asdict(score) for score in scaled_scores]
  680. ).fillna(0)
  681. # Perform clipping.
  682. df.value = df.value.clip(0, 1)
  683. print('Metrics after scaling and clipping:')
  684. print(df[['metric_id', 'value']])
  685. avg_score = df['value'].mean()
  686. return avg_score
  687. # %%
  688. # [exclude_from_prompt]
  689. from sklearn.decomposition import TruncatedSVD
  690. # Test on small subset:
  691. # Generate random unique indices for cells.
  692. random_cell_indices = np.random.choice(
  693. input_adata.shape[0], size=100, replace=False
  694. )
  695. small_adata = input_adata[random_cell_indices, :].copy()
  696. small_adata_pre_integration = adata_pre_integration[
  697. random_cell_indices, :
  698. ].copy()
  699. # Normalize to median total counts.
  700. sc.pp.normalize_total(small_adata)
  701. # Log transform the data.
  702. sc.pp.log1p(small_adata)
  703. X_input = small_adata.X
  704. n_components = 2
  705. pca = TruncatedSVD(n_components=n_components)
  706. X_output = pca.fit_transform(X_input)
  707. output = ad.AnnData(
  708. obs=small_adata.obs,
  709. var=small_adata.var,
  710. obsm={
  711. "X_emb": X_output,
  712. },
  713. )
  714. score(output, small_adata_pre_integration, bounds_map_train)
  715. # %% [markdown]
  716. # # Begin mutable cells
  717. # %%
  718. from typing import Any
  719. import jax
  720. from sklearn.decomposition import TruncatedSVD
  721. import tensorflow as tf
  722. import torch
  723. # Define parameters for the config.
  724. config = {}
  725. def eliminate_batch_effect_fn(
  726. adata: ad.AnnData, config: dict[str, Any]
  727. ) -> ad.AnnData:
  728. # Your code here to return ad.AnnData without batch variation.
  729. # %% [markdown]
  730. # # End mutable cells
  731. # %% [markdown]
  732. # # Validation
  733. # %%
  734. cell_type_info = input_adata.obs.pop('cell_type')
  735. output_adata = eliminate_batch_effect_fn(input_adata, config=config)
  736. output_adata.obs['cell_type'] = cell_type_info
  737. # %%
  738. validation_score = score(output_adata, adata_pre_integration, bounds_map_train)
  739. print(f'Validation Score: {validation_score}')
  740. # %%
  741. # [exclude_from_prompt]
  742. # Calculate scores based on the fixed validation set.
  743. if False:
  744. # Input data.
  745. input_adata_val = read_anndata(
  746. INPUT_FILE_VAL,
  747. X='layers/counts',
  748. obs='obs',
  749. var='var',
  750. )
  751. print(input_adata_val)
  752. # Data before integration, for computation of metrics.
  753. adata_pre_integration_val = read_anndata(
  754. INPUT_FILE_PRE_INTEGRATION_VAL,
  755. X='layers/normalized',
  756. obs='obs',
  757. var='var',
  758. uns='uns',
  759. )
  760. print(adata_pre_integration_val)
  761. # Bounds for scaling of metrics, for computation of metrics.
  762. df_score_bounds_val = pd.read_csv(INPUT_FILE_SCORE_BOUNDS_VAL)
  763. bounds_map_val = dataframe_to_bounds_map(df_score_bounds_val)
  764. cell_type_info_val = input_adata_val.obs.pop('cell_type')
  765. output_adata_val = eliminate_batch_effect_fn(input_adata_val, config=config)
  766. output_adata_val.obs['cell_type'] = cell_type_info_val
  767. validation_score_val = score(
  768. output_adata_val, adata_pre_integration_val, bounds_map_val
  769. )
  770. print(
  771. 'Validation Score (calculated using validation set):'
  772. f' {validation_score_val}'
  773. )
  774. # %%
  775. # [exclude_from_prompt]
  776. # Calculate scores based on the test set.
  777. if False:
  778. import time
  779. TEST_DATASETS = [
  780. 'dkd',
  781. 'gtex_v9',
  782. 'hypomap',
  783. 'immune_cell_atlas',
  784. 'mouse_pancreas_atlas',
  785. 'tabula_sapiens',
  786. ]
  787. INPUT_FILE_TEST_PATTERN = os.path.join(
  788. INPUT_FILE_DIR, '{dataset}-subsampled-dataset.h5ad'
  789. )
  790. INPUT_FILE_PRE_INTEGRATION_TEST_PATTERN = os.path.join(
  791. INPUT_FILE_DIR, '{dataset}-subsampled-solution.h5ad'
  792. )
  793. INPUT_BOUND_TEST = './datasets/single-cell-batch-integration/openproblems_metrics_bounds.csv'
  794. def evaluate_on_test(
  795. input_dataset: ad.AnnData,
  796. input_solution: ad.AnnData,
  797. input_bounds: pd.DataFrame,
  798. dataset_id: str,
  799. method_id: str = METHOD_ID,
  800. ) -> int:
  801. bounds_map = dataframe_to_bounds_map(df_score_bounds)
  802. cell_type_info_val = input_dataset.obs.pop('cell_type')
  803. try:
  804. output_adata = eliminate_batch_effect_fn(input_dataset, config=config)
  805. output_adata.obs['cell_type'] = cell_type_info_val
  806. validation_score = score(
  807. output_adata, input_solution, bounds_map, dataset_id, method_id
  808. )
  809. except Exception as e:
  810. print(f"Error during evaluating dataset {dataset_id}: {e}")
  811. validation_score = None
  812. return validation_score
  813. all_validation_scores = []
  814. for dataset_name in TEST_DATASETS:
  815. print(f'========Start evaluation on {dataset_name} dataset ========')
  816. print(f'========Reading {dataset_name} dataset from file ========')
  817. start = time.time()
  818. # Input data.
  819. input_adata = read_anndata(
  820. INPUT_FILE_TEST_PATTERN.format(dataset=dataset_name),
  821. X='layers/counts',
  822. obs='obs',
  823. var='var',
  824. )
  825. # Data before integration, for computation of metrics.
  826. adata_pre_integration = read_anndata(
  827. INPUT_FILE_PRE_INTEGRATION_TEST_PATTERN.format(dataset=dataset_name),
  828. X='layers/normalized',
  829. obs='obs',
  830. var='var',
  831. uns='uns',
  832. )
  833. t = time.time() - start
  834. print(
  835. f'========Finished loading {dataset_name} dataset. Took'
  836. f' {t} seconds.========'
  837. )
  838. # Bounds for scaling of metrics, for computation of metrics.
  839. df_score_bounds = pd.read_csv(INPUT_BOUND_TEST)
  840. print(f'========Scoring {dataset_name} dataset ========')
  841. start = time.time()
  842. validation_score = evaluate_on_test(
  843. input_dataset=input_adata,
  844. input_solution=adata_pre_integration,
  845. input_bounds=df_score_bounds,
  846. dataset_id=f'cellxgene_census/{dataset_name}',
  847. )
  848. t = time.time() - start
  849. print(f'========Finished scoring. Took {t} seconds.========')
  850. print(
  851. f'Validation Score (calculated using {dataset_name} set):'
  852. f' {validation_score}'
  853. )
  854. all_validation_scores.append(validation_score)
  855. # --- Calculate the two averages ---
  856. # 1. Average imputing None as 0
  857. scores_imputed_as_zero = [s if s is not None else 0 for s in all_validation_scores]
  858. average_imputed_zero = sum(scores_imputed_as_zero) / len(scores_imputed_as_zero)
  859. print(f'\n======== Average Validation Score (imputing None as 0): {average_imputed_zero:.4f} ========')
  860. # 2. Average only across available datasets (excluding None)
  861. available_scores = [s for s in all_validation_scores if s is not None]
  862. if available_scores: # Avoid division by zero if all scores are None
  863. average_available = sum(available_scores) / len(available_scores)
  864. print(f'======== Average Validation Score (only available datasets): {average_available:.4f} ========')
  865. else:
  866. print('======== No available validation scores to calculate average for available datasets. ========')
  867. # %%

single_cell_batch_integration.ipynb at commit b836730, under Apache-2.0 · at the source

Overview

Authors: Eser Aygün1, Anastasiya Belyaeva2, Gheorghe Comanici1, Marc Coram2, Hao Cui2, Jake Garrison3, Renee Johnston2, Anton Kast2, Cory Y. McLean2, Peter Norgaard2, Zahra Shamsi2, David Smalling1, James Thompson2, Subhashini Venugopalan2, Brian P. Williams2, Chujun He2,4, Sarah Martinson2,5, Martyna Plomecka2,6, Lai Wei2, Yuchen Zhou2
and 22 other authorsQian-Ze Zhu2,5, Matthew Abraham2, Erica Brand2, Anna Bulanova1, Jeffrey A. Cardille2,7, Chris Co2, Scott Ellsworth2, Grace Joseph2, Malcolm Kane2, Ryan Krueger2,5, Johan Kartiwa2, Dan Liebling2, Jan-Matthis Lueckmann2, Paul Raccuglia2, Xuefei Julie Wang2,8, Katherine Chou2, James Manyika2, Yossi Matias2, John C. Platt2, Lizzie Dorfman2, Shibl Mourad1, Michael P. Brenner2,5
  1. Google DeepMind, Montréal, Quebec Canada
  2. Google Research,Cambridge, MA USA
  3. Google Platforms and Devices, Mountain View, CA USA
  4. Massachusetts Institute of Technology,Cambridge, MA USA
  5. School of Engineering and Applied Sciences, Harvard University,Cambridge, MA USA
  6. Google DeepMind, New York, NY USA
  7. Faculty of Agricultural and Environmental Sciences, McGill University,Montréal, Quebec Canada
  8. California Institute of Technology,Pasadena, CA USA
Institutions: Google (United States) (United States); Massachusetts Institute of Technology (United States); Harvard University (United States); McGill University (Canada); California Institute of Technology (United States)
Journal: Nature, volume 654, issue 8120, pages 909-916
Dates: received 13 September 2025; accepted 13 May 2026; published online 19 May 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41586-026-10658-6 · PMID 42156545 · PMCID PMC13293872 · OpenAlex W4414757142
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), zebrafish (organism), other condition (population), methods / tools (subfield)
Methods: Smoothing, state filtering, decompositions, Machine learning
Keywords: Computational science, Computer science
MeSH: Artificial Intelligence*, Empirical Research*, Software*, Animals, Computational Biology, COVID-19, Forecasting, Humans, Large Language Models, SARS-CoV-2, Single-Cell Analysis, Zebrafish (* major topic)
Topic: Big Data and Business Intelligence (Management Information Systems, Business, Management and Accounting), according to OpenAlex
Citations: cited by 10 papers (Europe PMC); 70 references in the paper

Abstract

The cycle of scientific discovery is frequently bottlenecked by the slow, manual creation of software to support computational experiments1. To address this, we present Empirical Research Assistance (ERA), an artificial intelligence (AI) system that creates expert-level scientific software whose goal is to maximize a quality metric. The system uses a large language model (LLM) and tree search2 to systematically improve the quality metric and intelligently navigate the large space of possible solutions. ERA achieves expert-level results when it explores and integrates complex research ideas from external sources. The effectiveness of tree search is demonstrated across a diverse range of tasks. In bioinformatics, ERA discovered 40 new methods for single-cell data analysis that outperformed the top human-developed methods on a public leaderboard. In epidemiology, ERA generated 14 models that outperformed the Centers for Disease Control and Prevention (CDC) ensemble and all other individual models for forecasting COVID-19 hospitalizations. ERA also produced expert-level software for geospatial analysis, neural activity prediction in zebrafish and numerical solution of integrals, as well as a new rule-based construction for time-series forecasting. By devising and implementing new solutions to diverse tasks, ERA represents a notable step towards accelerating scientific progress.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 11 matches between paragraphs and lines of code.

salesforceairesearch/gift-eval

License: Apache-2.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 733645d21c9428cd249761b6ce2f3098f59b2f01, 28 September 2026
Languages: Jupyter (39), Python (12)
Size: 334 files, 51 scripts
Software Heritage: not archived
Found in: the text, “GIFT-Eval benchmark”
Holds: README, license file, environment (pyproject.toml, uv.lock), tests, 39 notebooks
Not found: CITATION.cff, continuous integration, documentation
Tools: pandas (38 files), NumPy (29 files), PyTorch (27 files), Matplotlib (6 files), Hugging Face Transformers (4 files), XGBoost (2 files)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
53 files

google-research/era

License: Apache-2.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: b836730b5c000526af95116b1d0e2c60c8cf0a10, 3 August 2026
Languages: Python (5), Jupyter (3), JavaScript (1)
Size: 6,584 files, 9 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, documentation
Not found: CITATION.cff, environment file, tests, continuous integration
Tools: NumPy (4 files), pandas (4 files), scikit-learn (3 files), Matplotlib (2 files), SciPy (2 files), anndata (1 file), h5py (1 file), JAX (1 file), PyTorch (1 file), Scanpy (1 file), TensorFlow (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
11 files

Code availability

A reference implementation of ERA is available at https://github.com/google-research/era. The best candidate solutions generated for each of the six scientific problems in this paper are publicly available at https://google-research.github.io/era, along with a user interface enabling examination of the full tree search data for a representative run for each of the six scientific problems. The interface allows inspecting the solution progression and breakthrough plot as the tree search proceeds and highlights code diffs.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 60 scripts, each with its path and the digest of its content;
  • 11 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Nature Portfolio

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 42 authors, 2 keywords, 12 MeSH terms, 35 references.

Cite

This paper

Aygün, E., Belyaeva, A., Comanici, G., Coram, M., Cui, H., Garrison, J., Johnston, R., Kast, A., McLean, C. Y., Norgaard, P., Shamsi, Z., Smalling, D., Thompson, J., Venugopalan, S., Williams, B. P., He, C., Martinson, S., Plomecka, M., Wei, L., . . . Brenner, M. P. (2026). An AI system to help scientists write expert-level empirical software. Nature, 654(8120), 909-916. https://doi.org/10.1038/s41586-026-10658-6

BibTeX

@article{aygun2026ai,
author = {Aygün, Eser and Belyaeva, Anastasiya and Comanici, Gheorghe and Coram, Marc and Cui, Hao and Garrison, Jake and Johnston, Renee and Kast, Anton and McLean, Cory Y. and Norgaard, Peter and Shamsi, Zahra and Smalling, David and Thompson, James and Venugopalan, Subhashini and Williams, Brian P. and He, Chujun and Martinson, Sarah and Plomecka, Martyna and Wei, Lai and Zhou, Yuchen and Zhu, Qian-Ze and Abraham, Matthew and Brand, Erica and Bulanova, Anna and Cardille, Jeffrey A. and Co, Chris and Ellsworth, Scott and Joseph, Grace and Kane, Malcolm and Krueger, Ryan and Kartiwa, Johan and Liebling, Dan and Lueckmann, Jan-Matthis and Raccuglia, Paul and Wang, Xuefei Julie and Chou, Katherine and Manyika, James and Matias, Yossi and Platt, John C. and Dorfman, Lizzie and Mourad, Shibl and Brenner, Michael P.},
title = {{An AI system to help scientists write expert-level empirical software}},
journal = {Nature},
year = {2026},
month = may,
volume = {654},
number = {8120},
pages = {909--916},
publisher = {Nature Portfolio},
issn = {0028-0836},
doi = {10.1038/s41586-026-10658-6},
url = {https://doi.org/10.1038/s41586-026-10658-6},
pmid = {42156545},
pmcid = {PMC13293872}
}

RIS

TY - JOUR
AU - Aygün, Eser
AU - Belyaeva, Anastasiya
AU - Comanici, Gheorghe
AU - Coram, Marc
AU - Cui, Hao
AU - Garrison, Jake
AU - Johnston, Renee
AU - Kast, Anton
AU - McLean, Cory Y.
AU - Norgaard, Peter
AU - Shamsi, Zahra
AU - Smalling, David
AU - Thompson, James
AU - Venugopalan, Subhashini
AU - Williams, Brian P.
AU - He, Chujun
AU - Martinson, Sarah
AU - Plomecka, Martyna
AU - Wei, Lai
AU - Zhou, Yuchen
AU - Zhu, Qian-Ze
AU - Abraham, Matthew
AU - Brand, Erica
AU - Bulanova, Anna
AU - Cardille, Jeffrey A.
AU - Co, Chris
AU - Ellsworth, Scott
AU - Joseph, Grace
AU - Kane, Malcolm
AU - Krueger, Ryan
AU - Kartiwa, Johan
AU - Liebling, Dan
AU - Lueckmann, Jan-Matthis
AU - Raccuglia, Paul
AU - Wang, Xuefei Julie
AU - Chou, Katherine
AU - Manyika, James
AU - Matias, Yossi
AU - Platt, John C.
AU - Dorfman, Lizzie
AU - Mourad, Shibl
AU - Brenner, Michael P.
TI - An AI system to help scientists write expert-level empirical software
T2 - Nature
J2 - Nature
PY - 2026
DA - 2026/05/19
VL - 654
IS - 8120
SP - 909
EP - 916
SN - 0028-0836
PB - Nature Portfolio
DO - 10.1038/s41586-026-10658-6
UR - https://doi.org/10.1038/s41586-026-10658-6
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41586-026-10658-6",
"type": "article-journal",
"title": "An AI system to help scientists write expert-level empirical software",
"container-title": "Nature",
"author": [
{
"family": "Aygün",
"given": "Eser"
},
{
"family": "Belyaeva",
"given": "Anastasiya"
},
{
"family": "Comanici",
"given": "Gheorghe"
},
{
"family": "Coram",
"given": "Marc"
},
{
"family": "Cui",
"given": "Hao"
},
{
"family": "Garrison",
"given": "Jake"
},
{
"family": "Johnston",
"given": "Renee"
},
{
"family": "Kast",
"given": "Anton"
},
{
"family": "McLean",
"given": "Cory Y."
},
{
"family": "Norgaard",
"given": "Peter"
},
{
"family": "Shamsi",
"given": "Zahra"
},
{
"family": "Smalling",
"given": "David"
},
{
"family": "Thompson",
"given": "James"
},
{
"family": "Venugopalan",
"given": "Subhashini"
},
{
"family": "Williams",
"given": "Brian P."
},
{
"family": "He",
"given": "Chujun"
},
{
"family": "Martinson",
"given": "Sarah"
},
{
"family": "Plomecka",
"given": "Martyna"
},
{
"family": "Wei",
"given": "Lai"
},
{
"family": "Zhou",
"given": "Yuchen"
},
{
"family": "Zhu",
"given": "Qian-Ze"
},
{
"family": "Abraham",
"given": "Matthew"
},
{
"family": "Brand",
"given": "Erica"
},
{
"family": "Bulanova",
"given": "Anna"
},
{
"family": "Cardille",
"given": "Jeffrey A."
},
{
"family": "Co",
"given": "Chris"
},
{
"family": "Ellsworth",
"given": "Scott"
},
{
"family": "Joseph",
"given": "Grace"
},
{
"family": "Kane",
"given": "Malcolm"
},
{
"family": "Krueger",
"given": "Ryan"
},
{
"family": "Kartiwa",
"given": "Johan"
},
{
"family": "Liebling",
"given": "Dan"
},
{
"family": "Lueckmann",
"given": "Jan-Matthis"
},
{
"family": "Raccuglia",
"given": "Paul"
},
{
"family": "Wang",
"given": "Xuefei Julie"
},
{
"family": "Chou",
"given": "Katherine"
},
{
"family": "Manyika",
"given": "James"
},
{
"family": "Matias",
"given": "Yossi"
},
{
"family": "Platt",
"given": "John C."
},
{
"family": "Dorfman",
"given": "Lizzie"
},
{
"family": "Mourad",
"given": "Shibl"
},
{
"family": "Brenner",
"given": "Michael P."
}
],
"container-title-short": "Nature",
"volume": "654",
"issue": "8120",
"page": "909-916",
"DOI": "10.1038/s41586-026-10658-6",
"PMID": "42156545",
"PMCID": "PMC13293872",
"ISSN": "0028-0836",
"publisher": "Nature Portfolio",
"URL": "https://doi.org/10.1038/s41586-026-10658-6",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
19
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1093/nar/gkag706 [code]
scDifformer: diffusion-based post-training for virtual cell modeling across large-scale single-cell data.
Journal: Nucleic acids research
In common: anndata, Scanpy, TensorFlow, 7 other tools, 3 references
[2] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: JAX, Hugging Face Transformers, XGBoost, 7 other tools, zebrafish
[3] doi:10.1038/s41592-026-03194-8 [code]
Beyond benchmarking: an expert-guided consensus approach to spatially aware clustering.
Journal: Nature methods
In common: anndata, Scanpy, TensorFlow, 6 other tools, methods / tools, 2 references
[4] doi:10.1038/s41592-026-03057-2 [code]
CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species.
Journal: Nature methods
In common: anndata, Scanpy, TensorFlow, 7 other tools, zebrafish, methods / tools
[5] doi:10.1038/s44320-026-00208-7 [code]
Interpretable deep generative ensemble learning for single-cell omics with Hydra.
Journal: Molecular systems biology
In common: anndata, Scanpy, TensorFlow, 7 other tools, 1 reference
[6] doi:10.1093/bib/bbag490 [code]
Systematic benchmarking and optimal strategy selection of cross-species integration methods.
Journal: Briefings in bioinformatics
In common: anndata, Scanpy, scikit-learn, 4 other tools, methods / tools, 3 references
[7] doi:10.1186/s12859-026-06490-4 [code]
Tissueformer: extending single-cell foundation models to predict population-level phenotypes.
Journal: BMC bioinformatics
In common: Hugging Face Transformers, anndata, PyTorch, 5 other tools, other condition, 2 references
[8] doi:10.1038/s41598-026-48613-0 [code]
An snRNA-seq aging clock for the fruit fly head sheds light on sex-biased aging.
Journal: Scientific reports
In common: XGBoost, anndata, Scanpy, 7 other tools
[9] doi:10.1186/s12864-026-12965-8 [code]
Systematic evaluation of single-cell foundation model interpretability: attention-derived edge scores add no incremental value over gene-level features for perturbation-target prediction.
Journal: BMC genomics
In common: Hugging Face Transformers, anndata, Scanpy, 7 other tools
[10] doi:10.64898/2026.03.30.714220 [code]
An integrated single cell and spatial omics atlas of human prenatal development
Journal: bioRxiv (preprint)
In common: Hugging Face Transformers, anndata, Scanpy, 7 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.