pypi ehrapy 0.16.0
0.16.0 🌈

2 hours ago

ehrapy 0.16.0 adds ep.ml to fit, evaluate, calibrate and explain prediction models on static and longitudinal EHR data. It also adds ep.tl.comorbidity_index for the Charlson and Elixhauser indices, competing risks in ep.tl.kaplan_meier, cohort definition by inclusion criteria with ep.pp.filter_observations, longitudinal quality control, PCA and FAMD, ep.pl.trajectories, and ep.pp.gradient_boosting_impute. Preprocessing supports numpy, scipy sparse and dask arrays for static 2D and longitudinal 3D data.

Warning

This release has breaking changes: the API conventions are unified (copy replaces inplace, keyword-only arguments), ep.pp.mice_forest_impute and several deprecated functions are removed, and survival, normalization and plotting defaults have changed. See 💥 Breaking changes below.

ehrapy now requires ehrdata>=0.5.0.

🚀 Features

  • ep.tl.comorbidity_index scores every patient with the Charlson or Elixhauser comorbidity index from ICD-9-CM and ICD-10 codes, and from SNOMED codes through the ICD codes mapped to them, using the coding algorithms of Quan et al. 2005 with the Charlson, Quan 2011 or van Walraven weights, and stores the score and every comorbidity in obs (#1161) @Zethson

  • ep.get.obs_df and the functions that read variables through it, the survival and regression models, the causal estimators, ep.pl.scatter, ep.pl.catplot and ep.pl.ols, take variables of longitudinal 3D data at their first non-missing value, the baseline, or at another statistic over time passed to obs_df as statistic (#1164) @Zethson

    The survival models derive the duration and event from an event_col that is a longitudinal variable, so that ep.tl.kaplan_meier(edata, event_col="SepsisLabel") estimates the time to sepsis on PhysioNet 2019.
    The event is whether the variable is ever 1, at the time of its first 1 in edata.tem["time_value"], and observations without one are censored at their last non-missing value.
    The fitted models hold the derived durations and events as durations and event_observed.

  • ep.pp.summarize_measurements summarizes the timepoints selected by tem_names, or several named windows such as the first 6 and the last 24 hours in columns {var}_{stat}_{window}, and ep.ml computes the features of its observation windows with it (#1159) @Zethson

  • ep.tl.kaplan_meier estimates the cumulative incidence of an event_of_interest with the Aalen-Johansen estimator when the event column holds competing events, ep.tl.nelson_aalen its cause-specific cumulative hazard, and ep.pl.kaplan_meier plots the cumulative incidence (#1155) @Zethson

    Without event_of_interest, the univariate survival models raise an error for event columns with more than one event type instead of silently treating every event type as the same event.

  • ep.pp.filter_observations defines a cohort by inclusion criteria in query on obs columns and variables, which for longitudinal data take the value at the timepoints selected by tem_names reduced by agg, and records every step in a ep.tl.CohortTracker passed as tracker for CONSORT-style flowcharts (#1157) @Zethson

  • ep.pp.pca and ep.tl.famd run on longitudinal data by unfolding every variable and timepoint into a feature, store loadings per variable and timepoint in varm, and ep.pp.neighbors uses the resulting X_pca for longitudinal .X (#1158) @Zethson

  • ep.pp.qc_metrics adds, for longitudinal data, the share of observations that measure every variable and the median time between its consecutive values to var, and the number of measured timepoints with the first and last measured time to obs, in the times of edata.tem[time_key] (#1156) @Zethson

  • ep.pp.qc_lab_measurements flags implausible jumps between consecutive values of longitudinal variables in obs, beyond an absolute or relative max_change per time for all or single variables, or else beyond the range of normal changes estimated with method (#1156) @Zethson

  • Models of time series in ep.ml measure the time since the last observation with the times of the timepoints in edata.tem[time_key], a new argument of ep.ml.fit, instead of counting timepoints, so that irregular timepoints are spaced correctly (#1151) @Zethson

  • ep.ml.Task with rolling=True predicts a longitudinal variable, such as the hourly sepsis label of PhysioNet 2019, at every timepoint from the timepoints before it, and every function of ep.ml fits, stores, evaluates, calibrates and explains these predictions per timepoint while keeping patients together (#1152) @Zethson

    On the held-out patients of PhysioNet 2019, a GRU predicts the hourly sepsis label from the hours before with an AUROC of 0.75 and AUPRC of 0.05, and gradient boosting on summaries of those hours with 0.67 and 0.02, for 1.2% positive hours.

  • ep.pp.summarize_measurements computes the number of values, their standard deviation and, for longitudinal data, their slope over time (#1150) @Zethson

  • ep.pl.trajectories plots the mean of longitudinal variables over time with a confidence band for every group of observations (#1146) @Zethson

  • ep.pp.locf_impute takes a limit on how many timepoints an observed value is carried forward, and its fallback only fills timepoints before a patient's first observation (#1147) @Zethson

  • ep.ml fits, applies and evaluates models that predict patient outcomes from static and longitudinal data (#1144) @Zethson

    ep.ml.split assigns patients to train, tuning and held_out sets, at random or by time, and ep.ml.fit fits imputation, scaling and the model on train only.
    A ep.ml.Task sets binary, multiclass, multilabel, regression or survival targets and the observation window that longitudinal variables are summarized over.
    ep.ml.fit trains linear, gradient boosting, random forest and Cox models, multilayer perceptrons, and GRU, LSTM, GRU-D, TCN, transformer and RETAIN models of time series, whose dependencies the new ml extra installs.
    ep.ml.evaluate reports the metrics of every kind of task with confidence intervals from resampled patients, overall, per subgroup and as differences between subgroups such as demographic parity and equalized odds.
    ep.ml.calibrate calibrates predicted probabilities with Platt scaling, isotonic regression or temperature scaling, and ep.ml.conformalize adds conformal prediction sets and intervals, both on the tuning set.
    ep.ml.permutation_importance stores how much every variable matters to a model in var and varm.
    ep.pl.prediction_performance plots the ROC, precision-recall and calibration curves, per class or label for multiclass and multilabel tasks, and ep.pl.subgroup_performance the metrics of every subgroup.
    On PhysioNet 2012, a GRU on the first 48 hours predicts in-hospital mortality with a held-out AUROC of 0.87 and AUPRC of 0.57, and gradient boosting on their summaries with 0.86 and 0.51.

  • ep.pp.gradient_boosting_impute imputes every variable with a gradient boosting model, which for longitudinal data also uses the closest observed values before and after each timepoint (#1135) @Zethson

    With every variable held out at 10% of the observed hours of all 11,988 PhysioNet 2012 patients, its RMSE on the standardized held-out values is 0.62 in 36 seconds, against 1.09 in 232 seconds for ep.pp.miss_forest_impute and 0.82 for ep.pp.locf_impute.

  • Preprocessing functions support numpy, scipy sparse and dask arrays, including dask arrays with sparse chunks, for static 2D and longitudinal 3D data (#1132) @Zethson

    Sparse arrays stay sparse and dask arrays stay lazy, and functions that store summaries compute once.
    The few combinations that cannot work this way raise a NotImplementedError that says why, such as centering or ComBat on sparse data, the faiss backend of ep.pp.knn_impute on sparse or dask arrays, and ep.pp.miss_forest_impute or ep.pp.detect_bias on dask arrays.

  • ep.pp.summarize_measurements aggregates longitudinal data over time into a 2D object with one column per variable and statistic (min, max, mean, median, first, last), which makes every 2D-only function usable on longitudinal data (#1132) @Zethson

  • ep.pp.winsorize, ep.pp.clip_quantile and ep.pp.qc_lab_measurements support longitudinal data (#1132) @Zethson

  • Tools, plots and ep.get functions support numpy, scipy sparse and dask arrays, and densify and compute only the variables they use (#1134) @Zethson

    The causal estimators, ep.tl.famd and ep.tl.rank_features_supervised accept sparse and dask arrays instead of rejecting or fully densifying them.
    Embedding plots such as ep.pl.umap color longitudinal data by obs columns, and ep.pl.timeseries plots 3D .X with layer=None.

💥 Breaking changes

  • ep.tl.weibull_aft takes event_col as keyword argument and defaults to model_ancillary=False like ep.tl.log_logistic_aft, so that it no longer models the shape parameter with all covariates unless asked to, which changes the fitted models of calls that relied on the default (#1193) @Zethson

  • Remove the single-cell leftovers feature_symbols, use_raw and the unused fields of ep.settings (#1154, #1162) @Zethson

    The feature_symbols argument of the ep.pl and ep.get functions and the use_raw argument of the ep.pl functions are gone, and plots always read .X or layer, never .raw.
    ep.settings keeps verbosity and n_jobs, the only fields ehrapy reads, and drops plot_suffix, file_format_data, file_format_figs, autosave, autoshow, writedir, cachedir, datasetdir, figdir, cache_compression, max_memory, categories_to_ignore and n_pcs.

  • The ep.pl.missing_values_* plots are interactive HoloViews plots that accept longitudinal data, and the missingno dependency is dropped (#1149) @Zethson

    ep.pl.missing_values_matrix shows the percentage of observed values per observation, or per timepoint for longitudinal data, and the bar plot, heatmap and dendrogram count every timepoint of every observation as one row.
    They take var_names, width, height and title instead of the missingno arguments.

  • ep.pl.timeseries plots the 3D .X by default instead of .layers["tem_data"], like every other function (#1121) @sueoglu

  • Survival analysis and regression models take their columns from edata.obs or variables and only drop observations missing a column the model uses (#1133) @Zethson

    ep.tl.kaplan_meier, ep.tl.cox_ph and the other survival fitters, ep.tl.ols and ep.tl.glm accept obs columns wherever they accept variables, and work on longitudinal data when every column they use lives in obs.
    Previously, every observation with a missing value in any variable was silently dropped, even in variables the model did not use.
    Regression fitters gained covariates, the univariate fitters take entry_col and weights_col instead of the entry and weights arrays, and numeric columns are passed to the models as numbers, so a binomial ep.tl.glm on a 0/1 outcome models the probability of 1.

  • Normalization functions take explicit parameters instead of forwarding **kwargs to scikit-learn or dask-ml (#1132) @Zethson

    ep.pp.scale_norm takes with_mean and with_std, ep.pp.minmax_norm feature_range, ep.pp.robust_scale_norm with_centering, with_scaling, quantile_range and unit_variance, ep.pp.quantile_norm n_quantiles, output_distribution, subsample and random_state, and ep.pp.power_norm method and standardize.
    A groupby column with missing values now raises instead of leaving those observations unnormalized, and the dask extra no longer installs dask-ml.

  • ep.pp.winsorize takes inclusive instead of **kwargs, ignores missing values when ranking, and cuts 1% from each side by default (#1132) @Zethson

    The previous default limits=(0.01, 0.99) cut 99% of the largest values, replacing almost every value of a variable with the same number.

  • ep.pp.regress_out no longer takes n_jobs (#1132) @Zethson

  • ep.pp.summarize_measurements no longer accepts statistics=None, which always raised (#1132) @Zethson

  • Remove ep.pp.mice_forest_impute and drop the miceforest dependency (#1097) @Zethson

    miceforest is effectively unmaintained (last commit 2025-10-27) and broken against lightgbm>=4.7.0, which it calls through a private, name-mangled internal (miceforest#104).
    Use ep.pp.gradient_boosting_impute or ep.pp.miss_forest_impute instead.
    For a LightGBM backend, pass IterativeImputer(estimator=LGBMRegressor(...)) directly.

  • Unify the API conventions across ep.pp, ep.tl, ep.pl and ep.get (#1126) @Zethson

    Required arguments are positional and every argument with a default is keyword-only.
    Grouping keys are called groupby (was group_key, cluster_key, balanced_key), feature subsets var_names (was vars, input_features, feature_names), feature-name columns feature_symbols (was gene_symbols, features), and result keys key_added when written and key when read (was uns_key).
    ep.pp.combat takes batch_key (was key), and ep.pp.pca and ep.pp.sample take edata (was data).

  • Replace inplace with copy (#1126) @Zethson

    ep.pp.combat, ep.pp.highly_variable_features, ep.tl.dendrogram and ep.tl.ingest take copy instead of inplace.
    ep.pp.qc_metrics, ep.pp.detect_bias, ep.tl.embedding_density, ep.tl.filter_rank_features_groups, ep.tl.rank_features_supervised and ep.tl.cox_ph_adjusted_curves gained copy.
    qc_metrics and detect_bias store their results in edata instead of returning them (detect_bias under uns["bias"]), and rank_features_supervised stores the model's test score in uns[key_added] instead of returning it.

  • Remove the deprecated ep.tl.kmf, ep.pp.subsample and the n_neighbours alias of ep.pp.knn_impute (#1126) @Zethson

🐛 Bug Fixes

  • ep.tl.aipw cross-fits its propensity and outcome models over n_folds folds assigned with random_state, which now defaults to 0, so that its standard error holds for flexible models such as gradient boosting, which on 21 baseline covariates of MIMIC-II gave an in-sample standard error of 0.010 against 0.048 when cross-fitted (#1200) @Zethson

  • ep.pl.subgroup_performance puts the difference between subgroups on a second line of every title, so that titles of metrics such as calibration_intercept are no longer cut off at the default width (#1197) @Zethson

  • ep.pp.detect_bias and ep.tl.rank_features_supervised no longer fail for variables whose name is also a column of obs or that have missing values, and leave out the observations without a value of the predicted variable or the sensitive feature (#1196) @Zethson

  • ep.tl.cox_ph_adjusted_curves raises an error when strata is neither a covariate nor one of the strata of cph, which gave every group the same curve, and its documentation no longer asks to leave strata out of the model (#1192) @Zethson

  • ep.pp.qc_metrics and ep.pp.qc_lab_measurements measure the longitudinal metrics and changes per time of 3D data in the unit of edata.tem["time_value"], such as days on ed.dt.mimic_iv_meds, instead of in seconds of edata.tem["interval_start_offset"], which they keep using when tem has no time_value (#1202) @Zethson

  • The confidence intervals of the subgroup "difference" of ep.ml.evaluate resample the difference between the subgroups with the highest and the lowest value instead of the largest difference of every bootstrap sample, which was never negative, so that they include 0 when the subgroups do not differ (#1198) @Zethson

  • ep.pl.cox_ph_forestplot draws the confidence intervals as horizontal error bars, puts the reference line at a coefficient of 0, which is a hazard ratio of 1, instead of 1, and gives the header its own row instead of overlapping the top coefficient (#1191) @Zethson

  • The survival models that derive durations from a longitudinal event_col measure them in the unit of edata.tem["time_value"], such as hours on PhysioNet 2012 and 2019, instead of in seconds of edata.tem["interval_start_offset"], which they keep using when tem has no time_value (#1190) @Zethson

  • The examples of ep.tl.kaplan_meier, ep.tl.nelson_aalen, ep.tl.weibull, ep.tl.weibull_aft, ep.tl.log_logistic_aft, ep.tl.cox_ph and ep.pl.kaplan_meier no longer flip censor_flg of MIMIC-II, which ed.dt.mimic_2 already codes as 1 for death, so that they model the time to death instead of the time to censoring (#1189) @Zethson

  • The calibration curve of ep.pl.prediction_performance ends its axes at the largest predicted probability or observed frequency instead of at 1, so that rare outcomes such as the hourly sepsis label of PhysioNet 2019 no longer squeeze it into a corner (#1201) @Zethson

  • The logistic propensity model of the causal estimators and diagnostics standardizes the covariates before fitting, so that estimates no longer depend on the units of the covariates and the fit converges on unscaled data such as the 21 baseline covariates of MIMIC-II (#1186) @Zethson

  • ep.pp.neighbors with metric="dtw", "soft_dtw" or "gak" stores the name of the metric in uns["neighbors"]["params"] instead of the internal distance function together with the whole time series array, so that ed.io.write_h5ed writes the result (#1188) @Zethson

  • ep.pl.violin no longer draws a legend that repeats the x axis when groupby has integer categories such as a 0/1 treatment flag (#1182) @Zethson

  • ep.ml.fit takes a trainer that sets how deep learning models are trained, which torch modules passed as model could not be given before (#1203) @Zethson

  • The summary() of a ep.tl.CausalEstimate labels the effect with its new estimand attribute, so that ep.tl.propensity_score_matching with target="att" reports an ATT instead of an ATE (#1183) @Zethson

  • ep.tl.s_learner, ep.tl.t_learner and ep.tl.x_learner take a random_state that seeds their built-in gradient boosting and random forest models, and the effect estimators pass their random_state to these models too, so that their estimates are reproducible (#1184) @Zethson

  • The causal estimators and diagnostics such as ep.tl.covariate_balance treat numeric variables of an object X, such as that of ed.dt.mimic_2(), as numeric instead of one-hot encoding each of their values (#1185) @Zethson

  • ep.pp.explicit_impute no longer warns for every variable missing from a replacement mapping, because leaving variables out of it is the intended way to impute only some of them (#1178) @Zethson

  • ep.tl.rank_features_groups reports the log2 ratio of the fractions of observations with value 1 as log fold change of binary categorical features and NaN for categorical features with more levels, instead of 1 regardless of direction, and orders features with equal adjusted p-values by their p-values (#1180) @Zethson

  • ep.tl.rank_features_groups tests numeric variables with missing values on their observed values instead of giving them a score of 0 and a p-value of 1 with t-tests, or ranking missing values as the largest with the Wilcoxon test, and computes their log fold changes from the observed values instead of returning NaN (#1179) @Zethson

  • ep.ml.calibrate defaults to method="platt" instead of "isotonic", because isotonic regression overfits tuning sets of typical size, such as on PhysioNet 2012 with a calibration slope of 0.54 against 0.95 with Platt scaling (#1169) @Zethson

  • ep.ml.evaluate and ep.pl.subgroup_performance keep the subgroups of groupby in the order of its categories, or of their appearance, instead of sorting them (#1169) @Zethson

  • ep.pp.summarize_measurements returns NaN without RuntimeWarnings for variables without values in a window of longitudinal data (#1169) @Zethson

  • ep.pp.gradient_boosting_impute leaves out predictors observed too rarely to split on instead of crashing when their few values all fall into the validation split of a model trained on more than 10,000 rows, and warns for variables that stay missing because they have no observed values in the training observations (#1174) @Zethson

  • ep.pp.winsorize and ep.pp.clip_quantile without var_names and obs_cols apply to all numeric variables instead of silently changing nothing (#1173) @Zethson

  • ep.pp.mcar_test tests longitudinal data at the timepoint selected by tem_names or reduced by agg over several timepoints, and raises an error naming the variables that are not observed together in at least two observations instead of returning a NaN p-value for Little's test (#1176) @Zethson

  • ep.tl.CohortTracker.plot_cohort_barplot with show=True lays out its rows next to the legend instead of squeezing them together by the height of the legend (#1177) @Zethson

  • Matplotlib figures in notebooks display inline after a holoviews-based ehrapy plot that runs before the first matplotlib figure, without %matplotlib inline (#1168) @Zethson

  • ep.tl.ols and ep.tl.glm find the obs columns and variables quoted as Q('...') in a formula, so that names with spaces work again, by requiring formulaic 1.2.2 (#1165) @Zethson

  • ep.pp.pca uses scanpy's dask-native covariance_eigh solver for dask arrays by default instead of requiring dask-ml (#1163) @Zethson

  • The rapids12 and rapids13 extras install the CUDA wheels of rapids-singlecell, whose 0.18 release only builds from source under its old name and extras (#1153) @Zethson

  • In ep.tl.rank_features_groups, the logfoldchanges of numeric features are the log2 ratio of the group mean to the reference mean instead of scanpy's fold change for log1p-transformed data, and categorical features with reference="rest" and a groups subset are compared to all other observations like numeric features (#1148) @Zethson

    The previous fold changes were meaningless on EHR data and NaN with a RuntimeWarning on standardized data, and observations outside groups were counted with the tested group.

  • ep.tl.rank_features_groups stores results only for the compared groups like scanpy, instead of also for the reference and for groups outside groups, which drew empty panels in ep.pl.rank_features_groups, and no longer crashes on categorical features with a groups subset (#1145) @Zethson

  • ep.pl.violin, ep.pl.scatter, ep.pl.ols and the causal estimators and diagnostics such as ep.tl.iptw work on 3D data when they only use obs columns, and ep.pl.dendrogram when the dendrogram is precomputed (#1143) @Zethson

  • ep.pp.neighbors uses the 3D .X by default with the time series metrics and rejects it with a clear error for other metrics instead of failing inside scikit-learn, and ep.tl.rank_features_groups rejects 3D data with the same error as other 2D-only functions (#1143) @Zethson

  • ep.pp.simple_impute, ep.pp.miss_forest_impute and ep.pp.locf_impute raise a KeyError for unknown var_names instead of imputing the last variable, and ep.pp.explicit_impute raises one for unknown replacement keys instead of ignoring them (#1140) @Zethson

    ep.pp.knn_impute and the normalization functions raise a KeyError for unknown var_names instead of reporting them as non-numeric.

  • Matplotlib figures in notebooks no longer disappear after the first holoviews-based ehrapy plot (#1136) @Zethson

  • ep.pp.knn_impute imputes all numeric variables by default instead of raising on data with encoded categorical variables (#1139) @Zethson

  • ep.pp.miss_forest_impute keeps only one forest in memory instead of every forest it fitted, cutting peak memory about sixfold, and is reproducible for a fixed random_state (#1135) @Zethson

  • On dask arrays with missing values, ep.pp.minmax_norm and ep.pp.robust_scale_norm returned all-NaN variables and ep.pp.quantile_norm returned wrong values (#1132) @Zethson

  • ep.pp.filter_features and ep.pp.filter_observations crashed for every non-numpy array, ep.pp.encode crashed on sparse arrays, and the imputers crashed on variables without any observed value (#1132) @Zethson

  • ep.pp.qc_metrics no longer computes dask arrays once per variable or reports all-NaN statistics when any variable holds strings (#1132) @Zethson

  • ep.tl.rank_features_groups no longer computes dask arrays once per group statistic or crashes with pts=True on sparse arrays, on 3D data with a single timepoint, or with num_cols_method="logreg", and its pts only hold the compared groups (#1134) @Zethson

  • ep.tl.filter_rank_features_groups no longer crashes on sparse arrays and ep.tl.dendrogram no longer crashes on scipy sparse matrices (#1134) @Zethson

  • ep.tl.tsne, ep.tl.dendrogram, ep.tl.ingest, the ep.pl.rank_features_groups_* plots, ep.pl.dpt_timeseries and embedding plots colored by variables reject 3D data with a clear error instead of failing inside scikit-learn or scanpy (#1134) @Zethson

  • The ep.pl.missing_values_* plots read only the missing value mask of the plotted variables instead of densifying or computing the whole data matrix (#1134) @Zethson

  • ep.pl.timeseries, ep.pl.sankey_diagram_time and ep.pl.ncp_cluster_trajectories compute only the plotted values, sankey_diagram_time no longer crashes on dask arrays or on a single timepoint, and ep.tl.ncp reports 2D sparse input as not 3D (#1134) @Zethson

  • ep.pp.explicit_impute() now accepts falsy mapping replacement values such as 0, 0.0, and empty strings (#1087) @driavysinus

  • ep.pp.knn_impute() now raises a clear NotImplementedError for unsupported array types (dask and sparse arrays) instead of failing silently (#1109) @sueoglu

  • _little_mcar_test now computes its global covariance matrix with true pairwise deletion instead of centering on the global mean, fixing incorrect p-values under moderate-to-high missingness (#1110) @sueoglu

  • ep.pp.encode and ep.pp.clip_quantile(copy=True) no longer modify their input (#1126) @Zethson

  • ep.tl.filter_rank_features_groups no longer raises KeyError: 'use_raw' (#1126) @Zethson

  • ep.tl.rank_features_supervised reports R² instead of accuracy for numeric targets (#1126) @Zethson

  • ep.tl no longer leaks implementation details such as np and sc, and its __all__ now lists leiden, dendrogram, dpt, paga and ingest; ep.pl gained an __all__ (#1126) @Zethson

  • ep.tl.famd works on 2D .X and layers with numeric or mixed variables, rejects 3D data, and stores per-variable loadings in .varm and all category loadings in .uns (#1129) @Zethson

  • ep.tl.ncp raises a ValueError for missing or negative values instead of returning NaN or negative factors (#1129) @Zethson

  • ep.pl.ols gained layer, supports sparse arrays and rejects 3D data instead of flattening the time axis into extra points (#1129) @Zethson

  • ep.get.obs_df raises a clear error when reading variables from 3D data and ep.get.var_df for any 3D data, instead of failing inside pandas (#1129) @Zethson

  • ep.tl.stratified_table_one stores its table with variable and level columns so that results can be written to h5ad (#1129) @Zethson

  • ep.tl.rank_features_groups supports categorical features in sparse arrays (#1129) @Zethson

  • ep.pp.neighbors with a time series metric no longer makes patients without comparable measurements everyone's nearest neighbours but leaves them unconnected (#1129) @Zethson

  • Align all scanpy wrappers with scanpy 1.12: the ep.pl.rank_features_groups_* plots read the ep.tl.rank_features_groups results by default, which now honours n_features and stores pts per feature, ep.pp.pca uses var["highly_variable"] by default as documented, every plot passes feature_symbols on to scanpy, and the deprecated save, ep.tl.umap(method=...) and the no-op n_bins of ep.pp.highly_variable_features are removed (#1130) @Zethson

📖 Documentation

  • Add general introductions to the API sections and redirect removed tutorials to the tutorial gallery (#1170) @Zethson

  • Refresh the README and installation guide (optional extras, install from GitHub) and drop dead Sphinx extensions (#1128) @Zethson

  • Add imputation methods tutorial notebook, benchmarking six imputation strategies on the PhysioNet2012 dataset (#1101) @sueoglu

  • Document the ep.get module, ep.tl.famd and ep.tl.anova_glm (#1126) @Zethson

  • Fix the ep.pl.kaplan_meier example (#1129) @Zethson

  • Describe ep.tl.ncp in plain words and drop the doubled period in the docs footer (#1131) @Zethson

  • Docstring examples keep time series in the 3D .X instead of .layers["tem_data"] and show the outputs the examples actually produce (#1098) @sueoglu

  • Describe ehrapy's data sources and analyses in the README, docs landing page and package metadata, and add a longitudinal quickstart, a sitemap, an llms.txt, stable canonical URLs and a CITATION.cff (#1141) @Zethson

  • Fix the docstring examples that no longer ran and add examples to ep.pp.pca, ep.pp.neighbors, ep.tl.umap, ep.tl.tsne, ep.tl.leiden and ep.tl.paga (#1142) @Zethson

    The tests now run every docstring example with xdoctest, so examples can no longer break silently.

🧰 Maintenance

  • Require ehrdata>=0.5.0 (#1212) @eroell
  • Require pynndescent<0.7 for now, as pynndescent 0.7 breaks ep.pp.neighbors and ep.tl.ingest through scanpy (scverse/scanpy#4444) (#1210) @eroell
  • ep.pp.qc_metrics with qc_vars sums sparse and dask arrays blockwise with fast_array_utils instead of rechunking dask arrays with sparse chunks (#1137) @Zethson
  • Run the imputation, causal inference and effect estimation tutorials in the notebook CI (#1120) @sueoglu
  • Update to cookiecutter-scverse v0.8.0, derive the version from git tags via hatch-vcs, and move the dev extra to a dev dependency group (#1125) @Zethson
  • import ehrapy no longer loads the holoviews extensions, which now load on the first holoviews-backed plot, and no longer installs a global SyntaxWarning filter (#1129) @Zethson
  • Drop the unused thefuzz, fhiry and filelock dependencies and move requests to the test extra (#1129) @Zethson
  • Remove dead code and let codecov compare project coverage against the base commit (#1129) @Zethson

Full Changelog: v0.15.0...v0.16.0

Don't miss a new ehrapy release

NewReleases is sending notifications on new releases.