OSCR

The gut microbiota and sleep in infants: a focus on diurnal rhythmicity patterns.

Code ↔ Paper

16 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 16 matches · 2 of them tie a paragraph to a whole file without given lines: weak matches, whose lines are not tinted
  1. [1] § Methods › Data processing › Infant gut microbiota › Sequences processing: gut microbiota composition and diversity. ↔ scripts/functions_script.py, lines 16 to 43 · score 0.86 · Faith phylogenetic diversity, Bray Curtis dissimilarity, Shannon entropy, unweighted UniFrac, Pielou evenness, Jaccard · Report a mismatch
  2. [2] § Methods › Data processing › Infant sleep › Sleep quality: infant sleep quality index (BabySQUID). ↔ scripts/2-EDA.ipynb, lines 137 to 154 · score 0.82 · sleep onset latencies, nighttime sleep duration, nighttime awakenings, babySQUID, BISQ, bedtime · Report a mismatch
  3. [3] § Results › Modulation of infant sleep by the gut microbiota › Sleep quality: BabySQUID ↔ scripts/2-EDA.ipynb, lines 137 to 154 · score 0.81 · sleep onset latency, nighttime sleep duration, nighttime awakenings, infant sleep BISQ, babySQUID, bedtime · Report a mismatch
  4. [4] § Methods › Data processing › Infant sleep › Sleep quality: infant sleep quality index (BabySQUID). ↔ scripts/functions_script.py, lines 347 to 385 · score 0.81 · min max, nighttime sleep duration, equal weights, reverse, awakenings, latency · Report a mismatch
  5. [5] § Methods › Statistical analysis › Modulation of infant sleep by the gut microbiota › Sleep quality: BabySQUID. ↔ scripts/functions_script.py, lines 1336 to 1448 · score 0.76 · random forest classifiers, cross validation, Accuracy, splits, trained, predict · Report a mismatch
  6. [6] § Methods › Data processing › Infant gut microbiota › Sequences processing: gut microbiota composition and diversity. ↔ scripts/1-seq-proc.ipynb, lines 116 to 162 · score 0.76 · classifier trained, sequencing depth, bp, RESCRIPt, SILVA, truncation · Report a mismatch
  7. [7] § Results › Modulation of infant sleep by the gut microbiota › Sleep rhythmicity: CFI ↔ scripts/functions_script.py, lines 16 to 43 · score 0.66 · Faith phylogenetic diversity, attuned care, Bray Curtis dissimilarity, temporal volatility, Jaccard, alpha diversity · Report a mismatch
  8. [8] § Methods › Statistical analysis › Exploratory analysis of the samples › Gut melatonin. ↔ scripts/6-melatonin.ipynb, lines 56 to 65 · score 0.65 · bowel movement, spent awake, gut melatonin, pg, stool · Report a mismatch
  9. [9] § Methods › Statistical analysis › Modulation of infant sleep by the gut microbiota › Sleep quality: BabySQUID. ↔ scripts/5.3-mb-sleep-RF.ipynb, lines 133 to 157 · score 0.64 · cross validation, splits, ASV, trained, harmonic, precision · Report a mismatch
  10. [10] § Results › Diurnal patterns along age › Gut microbiota rhythmicity and sleep rhythmicity ↔ scripts/1.2-meta-proc.ipynb, lines 402 to 426 · score 0.59 · Clostridium sensu stricto, Escherichia Shigella, Bacteroides · Report a mismatch
  11. [11] § Methods › Statistical analysis › Exploratory analysis of the samples › Gut melatonin. ↔ scripts/qiime2/14-diff-abund.sh, the whole file · a weak match · score 0.57 · ANCOM BC2, prevalence, filter, QIIME, abundance, melatonin · Report a mismatch
  12. [12] § Results › Modulation of infant sleep by the gut microbiota › Sleep rhythmicity: CFI ↔ scripts/functions_script.py, lines 45 to 57 · score 0.57 · Shannon entropy, Pielou evenness, phylogenetic diversity, temporal volatility, alpha diversity, Faith · Report a mismatch
  13. [13] § Results › Diurnal patterns along age › Microbial temporal volatility and age ↔ scripts/4-factors.ipynb, lines 93 to 116 · score 0.55 · Bray Curtis dissimilarity, unweighted UniFrac, Temporal volatility, distance, age · Report a mismatch
  14. [14] § Methods › Data processing › Infant gut microbiota › Stool processing: DNA extraction and 16S rRNA gene sequencing. ↔ scripts/qiime2/4-get_silva_reads_n_classifier.sh, the whole file · a weak match · score 0.54 · DNA, primers, V4, reverse, Amplicon, sequencing · Report a mismatch
  15. [15] § Results › Exploratory analyses of melatonin, sleep, and feeding history › Gut melatonin ↔ scripts/6-melatonin.ipynb, lines 78 to 101 · score 0.53 · bowel movement, spent awake, gut melatonin, feeding, age · Report a mismatch
  16. [16] § Methods › Data processing › Other variables ↔ scripts/2-EDA.ipynb, lines 187 to 206 · score 0.52 · spent awake, stool sample, feeding history, duration, clock, sleep · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 1,448 lines · 57 KB · no license · 5 matches

  1. import os
  2. import numpy as np
  3. import pandas as pd
  4. import matplotlib.pyplot as plt
  5. import seaborn as sns
  6. import qiime2 as q2
  7. from skbio import OrdinationResults
  8. from joypy import joyplot
  9. from scipy.optimize import curve_fit
  10. import matplotlib.lines as mlines
  11. from sklearn.model_selection import GroupShuffleSplit, GroupKFold, LeaveOneGroupOut
  12. from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
  13. from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error, accuracy_score, f1_score
  14. # mapping dictionary for vizualisation labels
  15. name_mapping = {
  16. "observed_features": "Observed features",
  17. "shannon_entropy": "Shannon entropy",
  18. "pielou_evenness": "Pielou evenness",
  19. "faith_pd": "Faith phylogenetic diversity",
  20. "age_days": "Age (days)",
  21. "sex[T.Male]": "Sex (male)",
  22. "BCQ_Attunement": "Attuned care score (BCQ)",
  23. "ASQ_Composite": "Infant behavioral \n development (ASQ)",
  24. "volatility_braycurtis": "Temporal volatility: \n Bray-Curtis dissimilarity",
  25. "volatility_jaccard": "Temporal volatility: \n Jaccard similarity index",
  26. "volatility_unweighted_unifrac": "Temporal volatility: \n Unweighted UniFrac distance",
  27. "volatility_weighted_unifrac": "Temporal volatility: \n Weighted UniFrac distance",
  28. "time_since_last_bowel_movement_in_h": "Time since last \n bowel movement (h)",
  29. "prior_time_spent_awake_in_h": "Prior time spent \n awake (h)",
  30. "time_since_last_feeding_in_h": "Time since last \n feeding (h)",
  31. "duration_of_last_sleep_in_h": "Duration of last \n sleep (h)",
  32. "R2_abundance": "Alpha diversity rhythmicity: \n cosine fit score of \n observed features",
  33. "R2_abundance_evenness": "Alpha diversity rhythmicity: \n cosine fit score of \n Shannon entropy",
  34. "R2_evenness": "Alpha diversity rhythmicity: \n cosine fit score of \n Pielou evenness",
  35. "R2_biodiversity": "Alpha diversity rhythmicity: \n cosine fit score of \n Faith's phylogenetic diversity",
  36. "std_times_between_feedings_in_h": "Std. dev. of time between \n feedings (h)",
  37. "R2_bifido": "$\it{Bifidobacterium}$ rhythmicity: \n cosine fit score of the $\it{Bifidobacterium}$ genus",
  38. "R2_veillo": "$\it{Veillonella}$ rhythmicity: \n cosine fit score of the $\it{Veillonella}$ genus",
  39. "R2_esche": "$\it{Escherichia-Shigella}$ rhythmicity: \n cosine fit score of the $\it{Escherichia-Shigella}$ genus",
  40. "R2_bacteroides": "$\it{Bacteroides}$ rhythmicity: \n cosine fit score of the $\it{Bacteroides}$ genus",
  41. "R2_clostridium": "$\it{Clostridium}$ rhythmicity: \n cosine fit score of the $\it{Clostridium}$ genus"}
  42. # labels in plots with bold formatting
  43. bold_labels = ["Observed features", "Shannon entropy", "Pielou evenness", "Faith phylogenetic diversity",
  44. "Temporal volatility: \n Bray-Curtis dissimilarity", "Temporal volatility: \n Jaccard similarity index",
  45. "Temporal volatility: \n Unweighted UniFrac distance", "Temporal volatility: \n Weighted UniFrac distance",
  46. "Alpha diversity rhythmicity: \n cosine fit score of \n observed features",
  47. "Alpha diversity rhythmicity: \n cosine fit score of \n Shannon entropy",
  48. "Alpha diversity rhythmicity: \n cosine fit score of \n Pielou evenness",
  49. "Alpha diversity rhythmicity: \n cosine fit score of \n Faith's phylogenetic diversity",
  50. "$\it{Bifidobacterium}$ rhythmicity: \n cosine fit score of the $\it{Bifidobacterium}$ genus",
  51. "$\it{Veillonella}$ rhythmicity: \n cosine fit score of the $\it{Veillonella}$ genus",
  52. "$\it{Escherichia-Shigella}$ rhythmicity: \n cosine fit score of the $\it{Escherichia-Shigella}$ genus",
  53. "$\it{Bacteroides}$ rhythmicity: \n cosine fit score of the $\it{Bacteroides}$ genus",
  54. "$\it{Clostridium}$ rhythmicity: \n cosine fit score of the $\it{Clostridium}$ genus"]
  55. def process_qza_file(qza_file_path, stat_function):
  56. """
  57. Processes a .qza file to load and transform data into a processed dataframe.
  58. Parameters:
  59. qza_file_path (str): Path to the input .qza file.
  60. stat_function (function): A function that takes a dataframe as input
  61. and returns a pocessed dataframe.
  62. Returns:
  63. pd.DataFrame: The processed dataframe.
  64. """
  65. # Load the Qiime 2 Artifact as a dataframe
  66. final_feature_table_df = load_qza_artifact(qza_file_path)
  67. # Perform function
  68. final_feature_table_percent = stat_function(final_feature_table_df)
  69. return final_feature_table_percent
  70. def compute_stats_for_percentage_columns(df):
  71. """
  72. Compute the mean, median, and standard deviation of all columns
  73. with names including the string "percentage".
  74. :param df: Input pandas DataFrame
  75. :return: DataFrame with computed statistics (mean, median, std)
  76. """
  77. # Filter columns that include the string "percentage" in their names
  78. percentage_columns = [col for col in df.columns if "percentage" in col]
  79. # Select only the percentage columns
  80. df_percentage = df[percentage_columns]
  81. # Compute statistics
  82. stats = {
  83. "mean": df_percentage.mean(),
  84. "median": df_percentage.median(),
  85. "std": df_percentage.std()
  86. }
  87. # Convert the dictionary of statistics into a DataFrame
  88. stats_df = pd.DataFrame(stats)
  89. return stats_df
  90. def load_qza_artifact(path, case='metadata'):
  91. """
  92. Loads a .qza artifact and converts it into a pandas DataFrame.
  93. Parameters:
  94. path (str): Path to the .qza file.
  95. case (str): Type of artifact to load ('metadata' or 'ordination').
  96. Returns:
  97. pd.DataFrame: Extracted data as a DataFrame.
  98. """
  99. artifact = q2.Artifact.load(path)
  100. if case == 'metadata':
  101. df = artifact.view(q2.Metadata).to_dataframe()
  102. elif case == 'ordination':
  103. df = artifact.view(OrdinationResults)
  104. else:
  105. raise ValueError(f"Unsupported case: {case}. Choose 'metadata' or 'ordination'.")
  106. return df
  107. def process_pcoa(pcs, metadata):
  108. """
  109. Extracts the first three PCoA axes and merges with metadata.
  110. Parameters:
  111. pcs (pd.DataFrame): The PCoA results containing samples.
  112. metadata (pd.DataFrame): The metadata to merge.
  113. Returns:
  114. pd.DataFrame: Processed DataFrame with the first 3 PCoA axes and metadata.
  115. """
  116. pcs_processed = pcs.samples.iloc[:, :3] # Take first 3 columns
  117. pcs_processed.columns = ['Axis 1', 'Axis 2', 'Axis 3'] # Rename columns
  118. return pd.concat([pcs_processed, metadata], join="inner", axis=1) # Merge with metadata
  119. def load_tsv(file_path, index_col=0):
  120. """
  121. Loads a .tsv file into a Pandas DataFrame.
  122. Parameters:
  123. file_path (str): Path to the .tsv file.
  124. index_col (int or None): Column to use as the row labels. Default is 0 (first column).
  125. Returns:
  126. pd.DataFrame: The loaded DataFrame.
  127. """
  128. return pd.read_csv(file_path, sep='\t', index_col=index_col)
  129. def save_df_as_tsv(df, file_path, index_name="id"):
  130. """
  131. Renames the index, saves a DataFrame as a .tsv file, and ensures proper formatting.
  132. Parameters:
  133. df (pd.DataFrame): The DataFrame to be saved.
  134. file_path (str): The output file path, including the filename (e.g., 'output/data.tsv').
  135. index_name (str): The name to assign to the index column (default: 'id').
  136. Returns:
  137. None
  138. """
  139. df = df.rename_axis(index_name) # Set index name
  140. df.to_csv(file_path, sep='\t', index=True) # Save as .tsv
  141. def get_taxonomy(col_name, level='p'):
  142. """
  143. Extracts a specific taxonomic level from a taxonomy string.
  144. Parameters:
  145. col_name (str): The taxonomy string (e.g., 'k__Bacteria; p__Firmicutes').
  146. level (str): The prefix to search for (e.g., 'p' for phylum, 'g' for genus).
  147. Returns:
  148. str: The identified taxonomic rank or a fallback 'Unassigned' string.
  149. """
  150. # Map prefixes to names for the return message
  151. ranks = {'k': 'kingdom', 'p': 'phylum', 'c': 'class', 'o': 'order', 'f': 'family', 'g': 'genus'}
  152. target = f"{level}__"
  153. # Split the taxonomy string by semicolon
  154. parts = col_name.split(';')
  155. # Find the part that starts with the target
  156. for part in parts:
  157. clean_part = part.strip()
  158. if clean_part.startswith(target):
  159. return clean_part
  160. return f"Unassigned {ranks.get(level, 'rank')}"
  161. def get_tax_per_timepoint(tax_ft, metdata_ft, timepoint_col):
  162. """
  163. Helper function to get the relative abundance of a taxonomic level per timepoint.
  164. """
  165. # Merge the taxonomic feature table with metadata
  166. df_with_metadata = tax_ft.merge(metdata_ft[timepoint_col], left_index=True, right_index=True)
  167. # Group by timepoint and sum
  168. df_with_metadata_timepoint = df_with_metadata.groupby(timepoint_col).sum()
  169. # Convert to relative abundance
  170. df_with_metadata_timepoint = df_with_metadata_timepoint.div(df_with_metadata_timepoint.sum(axis=1), axis=0)
  171. # Check that rows sum to 1
  172. row_sums = df_with_metadata_timepoint.sum(axis=1)
  173. if not np.all(np.isclose(row_sums, 1.0)):
  174. print("Warning: Some total rel. abund. do not sum to 1")
  175. return df_with_metadata_timepoint
  176. def collapse_taxa(df, min_rel_abundance=0, top_n=10, keep_col='Unassigned'):
  177. """
  178. Collapses a taxonomy DataFrame by retaining top taxa and grouping the rest into 'Others'.
  179. Parameters:
  180. df (pd.DataFrame): The DataFrame to be collapsed (samples as rows, taxa as columns).
  181. min_rel_abundance (float): The minimum abundance threshold for a taxon to be kept.
  182. top_n (int): The number of most abundant taxa to keep if min_rel_abundance is 0.
  183. keep_col (str): The name of a specific column to preserve from the ranking logic (default: 'Unassigned').
  184. Returns:
  185. pd.DataFrame: A DataFrame containing the top taxa, the preserved column, and a new 'Others' column.
  186. """
  187. # 1. Identify columns to rank (exclude 'Unassigned' from the ranking logic)
  188. cols_to_rank = [c for c in df.columns if c != keep_col]
  189. if min_rel_abundance > 0:
  190. # Filter columns based on the minimum relative abundance threshold
  191. print("Filtering taxa based on the minimum relative abundance threshold: ", min_rel_abundance)
  192. top_taxa = df.columns[df.max() > min_rel_abundance]
  193. else:
  194. # 2. Get the top N taxa based on the sum across all rows
  195. print("Selecting top N taxa based on total abundance: ", top_n)
  196. top_taxa = df[cols_to_rank].sum().sort_values(ascending=False).head(top_n).index.tolist()
  197. # 3. Identify columns that are not in the top N and not the protected 'Unassigned' column
  198. others_cols = [c for c in df.columns if c not in top_taxa and c != keep_col]
  199. # 4. Build the new DataFrame
  200. # Start with the top taxa
  201. top_taxa_sorted = df[top_taxa].sum().sort_values(ascending=False).index.tolist()
  202. df_collapsed = df[top_taxa_sorted].copy()
  203. # Add 'Unassigned' if it exists in the original data
  204. if keep_col in df.columns:
  205. df_collapsed[keep_col] = df[keep_col]
  206. # Sum the remaining columns into 'Others'
  207. df_collapsed['Others'] = df[others_cols].sum(axis=1)
  208. df_final = df_collapsed.copy()
  209. return df_final
  210. def plot_taxa_stacked_bar(df, level_name, out_path, threshold=0, top_n=10, figsize=(5.5, 4)):
  211. """
  212. Plots a stacked bar chart of relative abundance.
  213. Parameters:
  214. - df: The prepared DataFrame (rows=timepoints, cols=taxa)
  215. - level_name: String (e.g., 'Phylum' or 'Genus') for labels/titles
  216. - out_path: Directory path to save the PDF
  217. - threshold: Minimum max-abundance to include a taxon (default 1%)
  218. - top_n: Number of top taxa to include
  219. - figsize: Figure size tuple
  220. """
  221. if level_name == "phylum":
  222. keep_col = 'Unassigned phylum'
  223. elif level_name == "genus":
  224. keep_col = 'Unassigned genus'
  225. else:
  226. keep_col = 'Unassigned'
  227. # Filter for abundance threshold
  228. plot_df = collapse_taxa(df, min_rel_abundance=threshold, top_n=top_n, keep_col=keep_col)
  229. fig, ax = plt.subplots(1, 1, figsize=figsize)
  230. # plotting
  231. ax.stackplot(
  232. plot_df.index,
  233. plot_df.T,
  234. labels=plot_df.columns,
  235. colors=plt.cm.tab20.colors)
  236. # Legend handling: reverse order to match the visual stack (top-to-bottom)
  237. handles, labels = ax.get_legend_handles_labels()
  238. labels = [label.split('__')[-1] if '__' in label else label for label in labels]
  239. ax.legend(handles[::-1], labels[::-1], title=level_name,
  240. loc='upper left', bbox_to_anchor=(1, 1))
  241. # Aesthetics
  242. ax.tick_params(which="both", direction="out")
  243. ax.set_ylabel("Relative abundance")
  244. ax.set_title(f"Relative abundance of gut bacterial {level_name} over time")
  245. fig.tight_layout()
  246. # Save and show
  247. save_filename = f"{out_path}/{level_name.lower()}_relative_abundance_over_time.pdf"
  248. fig.savefig(save_filename, dpi=300, bbox_inches='tight')
  249. plt.show()
  250. def compute_distance_to_centroid(df, new_col_name, x_col='Axis 1', y_col='Axis 2', z_col='Axis 3',
  251. centroid_x_col='Centroid x', centroid_y_col='Centroid y', centroid_z_col='Centroid z'):
  252. """
  253. Computes the Euclidean distance from each point (x, y, z) to its centroid (centroid_x, centroid_y, centroid_z).
  254. Parameters:
  255. df (pd.DataFrame): DataFrame containing point and centroid coordinates.
  256. x_col, y_col, z_col (str): Column names for the point coordinates.
  257. centroid_x_col, centroid_y_col, centroid_z_col (str): Column names for the centroid coordinates.
  258. Returns:
  259. pd.DataFrame: Original DataFrame with an additional column 'distance_to_centroid'.
  260. """
  261. df[new_col_name] = np.sqrt(
  262. (df[x_col] - df[centroid_x_col])**2 +
  263. (df[y_col] - df[centroid_y_col])**2 +
  264. (df[z_col] - df[centroid_z_col])**2
  265. )
  266. return df[new_col_name]
  267. def min_max_normalize(series, min_val, max_val, reverse=False):
  268. """Normalize a value using min-max scaling to range [0,1]. Optionally reverse the scale."""
  269. normalized = (series - min_val) / (max_val - min_val)
  270. return 1 - normalized if reverse else normalized
  271. def compute_sleep_score(dataframe, weights=None):
  272. """
  273. Compute a composite sleep score for each row in a dataframe.
  274. Parameters:
  275. - df: DataFrame with columns ['nighttime_sleep_duration_in_h', 'sleep_latency_in_h', 'bedtime_in_h', 'number_of_awakenings']
  276. - weights: Dictionary with weights for each variable (default: equal weights)
  277. Returns:
  278. - Additional column 'sleep_score'
  279. """
  280. df = dataframe.copy()
  281. sleep_dur = min_max_normalize(df['nighttime_sleep_duration_in_h'],
  282. df['nighttime_sleep_duration_in_h'].min(),
  283. df['nighttime_sleep_duration_in_h'].max())
  284. sleep_lat = min_max_normalize(df['sleep_latency_in_h'],
  285. df['sleep_latency_in_h'].min(),
  286. df['sleep_latency_in_h'].max(), reverse=True)
  287. bedtime = min_max_normalize(df['bedtime_in_h'],
  288. df['bedtime_in_h'].min(),
  289. df['bedtime_in_h'].max(), reverse=True)
  290. awakenings = min_max_normalize(df['number_of_awakenings'],
  291. df['number_of_awakenings'].min(),
  292. df['number_of_awakenings'].max(), reverse=True)
  293. if weights is None:
  294. weights = { 'nighttime_sleep_duration_in_h': 0.25,
  295. 'sleep_latency_in_h': 0.25,
  296. 'bedtime_in_h': 0.25,
  297. 'number_of_awakenings': 0.25 }
  298. # Compute weighted sum
  299. df['composite'] = (weights['nighttime_sleep_duration_in_h'] * sleep_dur +
  300. weights['sleep_latency_in_h'] * sleep_lat +
  301. weights['bedtime_in_h'] * bedtime +
  302. weights['number_of_awakenings'] * awakenings)
  303. return df['composite']
  304. def cosine_function(x, A, phi, C):
  305. """
  306. Cosine function to model rhythmicity. Here lambda/period = 24h.
  307. """
  308. return A * np.cos((2 * np.pi / 24) * x + phi) + C
  309. def fit_cosine_and_plot(data, timepoint_values, timepoints_colors, feature_column, ax, show_legend=True):
  310. """
  311. Fits a cosine function to the given feature column for each timepoint and subject.
  312. Parameters:
  313. - data: DataFrame containing the dataset.
  314. - timepoint_values: List of timepoints to analyze.
  315. - timepoints_colors: List of colors corresponding to timepoints.
  316. - feature_column: Column name to analyze (e.g., "observed_features", "shannon_entropy").
  317. - ax: Matplotlib Axes object to plot on.
  318. - show_legend: Boolean, whether to display the legend.
  319. Returns:
  320. - fit_scores_df: DataFrame containing R², RMSE, and MAE for each subject.
  321. - summary_stats: Summary statistics (mean, median) per timepoint.
  322. """
  323. # Store results
  324. subject_fit_scores = []
  325. timepoint_counts = {} # Store (infants, samples) per timepoint
  326. legend_handles = {}
  327. for i, timepoint in enumerate(timepoint_values):
  328. subset_data_time = data[data['timepoint'] == timepoint]
  329. color = timepoints_colors[i]
  330. n_infants = 0
  331. n_samples = 0
  332. for subject in subset_data_time['infant_id'].unique():
  333. subset_data = subset_data_time[subset_data_time['infant_id'] == subject]
  334. if subset_data.empty or len(subset_data) < 4:
  335. continue # Skip subjects with too few points
  336. n_infants += 1
  337. n_samples += len(subset_data)
  338. X = np.array(subset_data['hour_stool_sample'])
  339. y = np.array(subset_data[feature_column])
  340. # Initial parameter guesses
  341. # Amplitude (A): Half the range of y_data.
  342. # Phase (φ): Start at 0.
  343. # Offset (C): Mean of y_data.
  344. A_init = (np.max(y) - np.min(y)) / 2
  345. phi_init = 0
  346. C_init = np.mean(y)
  347. initial_guess = [A_init, phi_init, C_init]
  348. params, _ = curve_fit(cosine_function, X, y, p0=initial_guess, maxfev=5000,
  349. bounds=([0, -np.pi, np.min(y)], [np.inf, np.pi, np.max(y)]))
  350. A_fit, phi_fit, C_fit = params
  351. # Generate fit curve
  352. X_fit = np.linspace(0, 24, 100)
  353. y_fit = cosine_function(X_fit, A_fit, phi_fit, C_fit)
  354. scatter_handle = ax.scatter(X, y, color=color, alpha=0.6)
  355. ax.plot(X_fit, y_fit, color=color, linewidth=2)
  356. if timepoint not in legend_handles:
  357. legend_handles[timepoint] = scatter_handle
  358. # Compute fit quality metrics
  359. r2 = r2_score(y, cosine_function(X, A_fit, phi_fit, C_fit))
  360. rmse = np.sqrt(mean_squared_error(y, cosine_function(X, A_fit, phi_fit, C_fit)))
  361. mae = mean_absolute_error(y, cosine_function(X, A_fit, phi_fit, C_fit))
  362. # Store results
  363. subject_fit_scores.append({"infant_id": subject,
  364. "timepoint": timepoint,
  365. "R²": r2,
  366. "RMSE": rmse,
  367. "MAE": mae,
  368. "amplitude": A_fit,
  369. "constant": C_fit})
  370. timepoint_counts[timepoint] = (n_infants, n_samples)
  371. # Convert results to DataFrame
  372. fit_scores_df = pd.DataFrame(subject_fit_scores)
  373. # Compute summary stats per timepoint
  374. summary_stats = fit_scores_df.groupby("timepoint").agg({
  375. "R²": ["mean", "median"],
  376. "RMSE": ["mean", "median"],
  377. "MAE": ["mean", "median"],
  378. "amplitude": "median",
  379. "constant": "median"
  380. }).reset_index()
  381. legend_labels = [f"{tp} ({timepoint_counts[tp][0]} infants, {timepoint_counts[tp][1]} samples)"
  382. for tp in legend_handles.keys()]
  383. ax.set_title(f"Cosine fit of {feature_column.replace('_', ' ')}", fontsize=16)
  384. ax.set_xlabel("hour_stool_sample", fontsize=14)
  385. ax.set_ylabel(feature_column.replace("_", " ").title(), fontsize=14)
  386. ax.grid(True)
  387. ax.set_xticks(range(0, 25, 4))
  388. if show_legend:
  389. ax.legend(legend_handles.values(), legend_labels, title="", bbox_to_anchor=(1, 1), loc='upper left', fontsize=14)
  390. return fit_scores_df, summary_stats
  391. def fit_cosine_and_plot_scaled(data, timepoint_values, timepoints_colors, feature_column,
  392. ax, fit_scores, show_legend=True, median_amplitudes=None, median_constants=None):
  393. """
  394. Plots a scaled cosine fit using precomputed median amplitude and constant per timepoint.
  395. Parameters:
  396. - data: DataFrame containing the dataset.
  397. - timepoint_values: List of unique timepoints.
  398. - timepoints_colors: List of colors corresponding to timepoints.
  399. - feature_column: Column to analyze (e.g., "observed_features", "shannon_entropy").
  400. - ax: Matplotlib Axes object to plot on.
  401. - show_legend: Boolean, whether to display the legend.
  402. - median_amplitudes: Precomputed dictionary of median amplitude per timepoint.
  403. - median_constants: Precomputed dictionary of median constant per timepoint.
  404. Returns:
  405. - None
  406. """
  407. if median_amplitudes is None or median_constants is None:
  408. raise ValueError("median_amplitudes and median_constants must be provided.")
  409. timepoint_counts = {} # Store (infants, samples) per timepoint
  410. legend_handles = {}
  411. # Plot scaled version using precomputed median values
  412. for i, timepoint in enumerate(timepoint_values):
  413. subset_data_time = data[data['timepoint'] == timepoint]
  414. subset_R2_time = fit_scores[fit_scores['timepoint'] == timepoint]
  415. color = timepoints_colors[i]
  416. n_infants = 0
  417. n_samples = 0
  418. for subject in subset_data_time['infant_id'].unique():
  419. subset_data = subset_data_time[subset_data_time['infant_id'] == subject]
  420. subset_R2 = subset_R2_time[subset_R2_time['infant_id'] == subject]
  421. if subset_data.empty or len(subset_data) < 4:
  422. continue
  423. if subset_R2["R²"].iloc[0] < 0.5:
  424. continue
  425. n_infants += 1
  426. n_samples += len(subset_data)
  427. X = np.array(subset_data['hour_stool_sample'])
  428. y = np.array(subset_data[feature_column])
  429. # Initial parameter guesses:
  430. # Amplitude (A): Half the range of y_data.
  431. # Phase (φ): Start at 0.
  432. # Offset (C): Mean of y_data.
  433. A_init = (np.max(y) - np.min(y)) / 2
  434. phi_init = 0
  435. C_init = np.mean(y)
  436. initial_guess = [A_init, phi_init, C_init]
  437. params, _ = curve_fit(cosine_function, X, y, p0=initial_guess, maxfev=5000,
  438. bounds=([0, -np.pi, np.min(y)], [np.inf, np.pi, np.max(y)]))
  439. A_fit, phi_fit, C_fit = params
  440. X_fit = np.linspace(0, 24, 100)
  441. A_fit = list(median_amplitudes)[i][timepoint]
  442. C_fit = list(median_constants)[i][timepoint]
  443. y_fit = cosine_function(X_fit, A_fit, phi_fit, C_fit)
  444. scatter_handle = ax.scatter(X, y, color=color, alpha=0.6)
  445. ax.plot(X_fit, y_fit, color=color, linewidth=2)
  446. if timepoint not in legend_handles:
  447. legend_handles[timepoint] = scatter_handle
  448. timepoint_counts[timepoint] = (n_infants, n_samples)
  449. legend_labels = [f"{tp} ({timepoint_counts[tp][0]} infants, {timepoint_counts[tp][1]} samples)" for tp in legend_handles.keys()]
  450. ax.set_title(f"Cosine fit (R² > 0.5) of {feature_column.replace('_', ' ')} \n - Adjusted by Median Amplitude & Constant", fontsize=16)
  451. ax.set_xlabel("hour_stool_sample", fontsize=14)
  452. ax.set_ylabel(feature_column.replace("_", " ").title(), fontsize=14)
  453. ax.grid(True)
  454. ax.set_xticks(range(0, 25, 4))
  455. if show_legend:
  456. ax.legend(legend_handles.values(), legend_labels, title="", loc='lower center', fontsize=14)
  457. def blend_with_white(color, blend_factor=0.3):
  458. """
  459. Blends a given RGB color with white.
  460. `blend_factor=0.3` means 30% white, 70% original color.
  461. """
  462. white = np.array([1, 1, 1, 1]) # RGBA white
  463. return tuple((1 - blend_factor) * np.array(color) + blend_factor * white)
  464. def samples_scatter_plot(data_analysis, data_all, timepoints, output_file):
  465. """
  466. Creates a scatter plot of sample collection times with vertical bars for additional samples.
  467. Parameters:
  468. data_analysis (DataFrame): Data containing 'hour_stool_sample', 'timepoint', and 'infant_id'.
  469. data_all (DataFrame): Data containing additional samples with 'hour_stool_sample', 'timepoint', and 'infant_id'.
  470. timepoints (list): List of timepoints to include in the plot (e.g., ['2 months', '4 months', '6 months']).
  471. output_file (str): Name of the file to save the plot.
  472. """
  473. num_plots = len(timepoints)
  474. fig, axes = plt.subplots(1, num_plots, figsize=(6 * num_plots, 6), sharey=True)
  475. # Sort infants by infant_id
  476. infants = sorted(data_analysis['infant_id'].unique()) # Sorting the infant IDs
  477. infants_num = list(range(1, len(data_analysis['infant_id'].unique()) + 1))
  478. y_positions = range(len(infants))
  479. for i, timepoint in enumerate(timepoints):
  480. ax = axes[i]
  481. timepoint_data_analysis = data_analysis[data_analysis['timepoint'] == timepoint]
  482. # Count valid samples for the current timepoint
  483. num_samples = timepoint_data_analysis.dropna(subset=['hour_stool_sample']).shape[0]
  484. # Add vertical bars for the samples from `data_all` first, to be behind the scatter points
  485. timepoint_data_all = data_all[data_all['timepoint'] == timepoint]
  486. for j, infant in enumerate(infants):
  487. infant_data_all = timepoint_data_all[timepoint_data_all['infant_id'] == infant]
  488. valid_data_all = infant_data_all.dropna(subset=['hour_stool_sample'])
  489. # Increase the length of the vertical bars (set y-axis range from j-0.5 to j+0.5)
  490. ax.vlines(valid_data_all['hour_stool_sample'], j - 0.5, j + 0.5, color='black', alpha=0.4)
  491. # Now plot the scatter plot for the data_analysis (dots will be in front)
  492. for j, infant in enumerate(infants):
  493. infant_data_analysis = timepoint_data_analysis[timepoint_data_analysis['infant_id'] == infant]
  494. valid_data_analysis = infant_data_analysis.dropna(subset=['hour_stool_sample'])
  495. ax.scatter(valid_data_analysis['hour_stool_sample'], [j] * len(valid_data_analysis), alpha=1)
  496. ax.set_xticks(range(0, 24, 4)) # Add x-axis ticks for time of day
  497. ax.set_xlim(0, 24) # Set x-axis limit from 0 to 24
  498. ax.set_xlabel('Time of day (hours)', fontsize=14)
  499. ax.set_title(f'Sample collection times at {timepoint} ({num_samples} samples)', fontsize=14)
  500. ax.grid(axis='y')
  501. # Set common y-axis label
  502. axes[0].set_yticks(y_positions)
  503. axes[0].set_yticklabels(infants_num)
  504. axes[0].set_ylabel('Infants', fontsize=14)
  505. plt.tight_layout()
  506. # Save and show the plot
  507. plt.savefig(output_file, dpi=300, bbox_inches='tight')
  508. plt.show()
  509. def density_plot(data_analysis, timepoints, colors, output_file, alpha=1, ticklabel=14, labelsize=16, titlesize=16):
  510. """
  511. Creates a density plot (joyplot) for sample collection times with sample counts included in the labels.
  512. Parameters:
  513. data_analysis (DataFrame): Data containing 'hour_stool_sample' and 'timepoint' columns.
  514. timepoints (list): List of timepoints to include in the plot (e.g., ['2 months', '4 months', '6 months']).
  515. colors (list): List of colors for the density plots, one per timepoint.
  516. alpha (float): Transparency level for the density plots.
  517. ticklabel (int): Font size for tick labels.
  518. labelsize (int): Font size for axis labels.
  519. titlesize (int): Font size for the plot title.
  520. output_file (str): Name of the file to save the plot.
  521. """
  522. # Creating a dictionary of data for each timepoint and calculating sample sizes
  523. timepoint_data = {}
  524. updated_timepoints = []
  525. for timepoint in timepoints:
  526. subset = data_analysis[data_analysis['timepoint'] == timepoint]['hour_stool_sample'].dropna()
  527. timepoint_data[timepoint] = subset
  528. n_samples = len(subset)
  529. updated_timepoints.append(f"(n={n_samples} samples)")
  530. ls_colors = colors # List of colors for each timepoint
  531. # Generate joyplots
  532. fig, ax = joyplot(
  533. data=list(timepoint_data.values()), # The list of sample hour data for each timepoint
  534. labels=updated_timepoints, # Updated labels with sample sizes
  535. overlap=0, # Control overlap between plots
  536. alpha=alpha * 0.9,
  537. ylabelsize=ticklabel,
  538. xlabelsize=ticklabel,
  539. color=ls_colors, # Color for each plot
  540. linewidth=0.8, # Line width for the outlines
  541. figsize=(6, 7), # Size of the plot
  542. )
  543. # Set x-axis limits and ticks
  544. for axis in ax:
  545. axis.set_xlim(0, 24)
  546. axis.set_xticks([0, 6, 12, 18, 24])
  547. # Set y-axis label
  548. # Customize plot appearance
  549. plt.rc("font", size=labelsize)
  550. plt.title("", fontsize=titlesize, y=0.9)
  551. plt.xlabel("Time of the day (hours)", fontsize=labelsize)
  552. plt.grid(True, axis='x')
  553. plt.savefig(output_file, dpi=300, bbox_inches='tight')
  554. plt.show()
  555. def generate_color_palette(list):
  556. """
  557. Generates a custom color palette composed of lightened shades derived from
  558. selected colors within the Spectral colormap.
  559. Parameters:
  560. list (list): A list of three integers specifying the number of shades to generate
  561. from each of the three selected base colors.
  562. """
  563. # Get colors from the Spectral colormap
  564. colors = [plt.cm.Spectral(i / float(6)) for i in range(7)]
  565. timepoints_colors = [colors[0], colors[5], colors[6]]
  566. # Function to generate shades of a color
  567. def generate_shades(base_color, num_shades, lighten=True):
  568. base_rgb = np.array(base_color[:3]) # Ignore alpha channel
  569. white_or_black = np.array([1, 1, 1]) if lighten else np.array([0, 0, 0])
  570. shades = [tuple((1 - t) * base_rgb + t * white_or_black) for t in np.linspace(0, 1, num_shades)]
  571. return shades
  572. # Generate shades
  573. shades_0 = generate_shades(timepoints_colors[0], list[0], lighten=True) # Lightening colors[0]
  574. shades_5 = generate_shades(timepoints_colors[1], list[1], lighten=True) # Lightening colors[5]
  575. shades_6 = generate_shades(timepoints_colors[2], list[2], lighten=True) # Lightening colors[6]
  576. # Combine all shades
  577. final_palette = shades_0 + shades_5 + shades_6
  578. return final_palette
  579. def plot_pcoa_infants(data, column, title_suffix, ax, var_explained, final_palette="husl"):
  580. """
  581. Function to plot a PCoA scatterplot grouped by infant_id with no specific hue order
  582. and without displaying the legend.
  583. Parameters:
  584. - data: DataFrame containing the PCoA data with 'Axis 1', 'Axis 2', and the grouping column.
  585. - column: Column to use for hue (e.g., 'infant_id').
  586. - title_suffix: String to be added to the plot title.
  587. - ax: Axis object on which to plot the graph.
  588. - var_explained: List containing the percentage of variance explained by Axis 1 and Axis 2.
  589. - final_palette: Color palette to use for the plot (default is "husl").
  590. """
  591. # Get unique colors from the scatterplot
  592. data = data.sort_values(column)
  593. unique_infants = data[column].unique()
  594. if final_palette=="husl":
  595. palette = sns.color_palette("husl", len(unique_infants))
  596. else:
  597. palette = final_palette
  598. # Create the scatterplot on the given axis
  599. scatter = sns.scatterplot(data=data,
  600. x='Axis 1',
  601. y='Axis 2',
  602. hue=column,
  603. palette=palette, # Use a diverse color palette
  604. ax=ax,
  605. legend=False) # Disable the legend
  606. # Calculate centroids for each group (infant_id in this case)
  607. centroids = data.groupby(column)[['Axis 1', 'Axis 2']].mean().reset_index()
  608. color_map = dict(zip(unique_infants, palette))
  609. # Overlay filled, round centroids for each infant_id
  610. for _, row in centroids.iterrows():
  611. ax.scatter(row['Axis 1'],
  612. row['Axis 2'],
  613. color=color_map[row[column]],
  614. s=300,
  615. alpha=0.6, # Adjust alpha for shading
  616. edgecolor='black', # Add edge for better visibility
  617. linewidth=1.2)
  618. ax.set_xlabel(f"Axis 1 ({var_explained[0]})")
  619. ax.set_ylabel(f"Axis 2 ({var_explained[1]})")
  620. # Add title and labels
  621. ax.set_title(f"PCoA with distance matrix as {title_suffix}", fontsize=15)
  622. ax.grid(True)
  623. def violinplot_with_lines(data, x_column, y_column, subject_column, title, y_column_name, palette,
  624. ax=None, fontsize_title=14, save_path=None, text=None, loc=0.05):
  625. """
  626. Creates a violin plot with overlaid boxplots and subject-level connecting lines
  627. across timepoints (or any ordered x-axis variable).
  628. Parameters
  629. ----------
  630. data : pandas.DataFrame
  631. The dataset containing all required columns.
  632. x_column : str
  633. Column name for the categorical x-axis variable (e.g., timepoint).
  634. y_column : str
  635. Column name for the numerical y-axis variable.
  636. subject_column : str
  637. Column name identifying subjects for drawing repeated-measure lines.
  638. title : str
  639. Title of the plot.
  640. y_column_name : str
  641. Y-axis label.
  642. palette : list or dict
  643. Color palette to assign to each x-axis category for violin and boxplots.
  644. ax : matplotlib.axes.Axes, optional
  645. Axis to draw on. If None, a new figure and axis are created.
  646. fontsize_title : int, optional (default = 14)
  647. Font size for the plot title. Reduced automatically when using subplots.
  648. save_path : str, optional
  649. If provided, saves the figure to this path (only if a new figure is created).
  650. text : str, optional
  651. Additional annotation text to display in the top-left corner of the plot.
  652. loc : float, optional (default = 0.05)
  653. Horizontal position (axis fraction) of the annotation text.
  654. """
  655. # Sort data by x-axis values
  656. data = data.sort_values([x_column])
  657. timepoint_order = data[x_column].unique()
  658. # Get count of each timepoint
  659. count_infants = data[x_column].dropna().value_counts().sort_index()
  660. x_labels = [f"{tp} (n={count_infants[tp]})" for tp in count_infants.index]
  661. # If ax is not provided, create a new figure and axis
  662. created_fig = False
  663. if ax is None:
  664. fig, ax = plt.subplots(figsize=(8, 6))
  665. created_fig = True # Track if we created a new figure
  666. else:
  667. fontsize_title = 16 # Reduce title size for subplots
  668. # Create violin plot
  669. sns.violinplot(x=data[x_column], y=data[y_column], palette=palette, ax=ax, showfliers=False, cut=0)
  670. # Overlay boxplot
  671. sns.boxplot(x=data[x_column], y=data[y_column], saturation=0.3, width=0.4, palette=palette,
  672. boxprops={'zorder': 2}, ax=ax)
  673. # Overlay individual subject lines
  674. x_positions = {tp: i for i, tp in enumerate(timepoint_order)}
  675. for subject_id, sub_df in data.groupby(subject_column):
  676. sub_df = sub_df.sort_values(x_column)
  677. ax.plot([x_positions[tp] for tp in sub_df[x_column]], sub_df[y_column], linestyle='-',
  678. alpha=0.2, color='black')
  679. # Set titles and labels
  680. ax.set_title(title, fontsize=fontsize_title)
  681. ax.set_xlabel(None)
  682. ax.set_ylabel(y_column_name, fontsize=14)
  683. ax.grid(True)
  684. # Rotate x-axis labels if using subplots
  685. rotation_angle = 45 if not created_fig else 0
  686. ax.set_xticklabels(x_labels, fontsize=12, rotation=rotation_angle, ha="right" if rotation_angle else "center")
  687. # Add optional text annotation
  688. if text is not None:
  689. ax.text(loc, 0.95, text, transform=ax.transAxes, fontsize=9, verticalalignment='top',
  690. horizontalalignment='left', bbox=dict(facecolor='white', alpha=0.5))
  691. # Save the figure if path is provided
  692. if save_path and created_fig:
  693. plt.savefig(save_path, dpi=300, bbox_inches='tight')
  694. # If a new figure was created, show it
  695. if created_fig:
  696. plt.show()
  697. return ax # Return axis for further modifications if needed
  698. def violinplot_no_lines(data, x_column, y_column, title, y_column_name, palette,
  699. ax=None, fontsize_title=16, fontsize_axes=14, save_path=None, text=None, loc=0.05):
  700. """
  701. Creates a violin plot with overlaid boxplots.
  702. Parameters
  703. ----------
  704. data : pandas.DataFrame
  705. The dataset containing all required columns.
  706. x_column : str
  707. Column name for the categorical x-axis variable (e.g., timepoint).
  708. y_column : str
  709. Column name for the numerical y-axis variable.
  710. title : str
  711. Title of the plot.
  712. y_column_name : str
  713. Y-axis label.
  714. palette : list or dict
  715. Color palette to assign to each x-axis category for violin and boxplots.
  716. ax : matplotlib.axes.Axes, optional
  717. Axis to draw on. If None, a new figure and axis are created.
  718. fontsize_title : int, optional (default = 16)
  719. Font size for the plot title. Reduced automatically when using subplots.
  720. fontsize_axes : int, optional (default = 14)
  721. Font size for axis labels and tick labels.
  722. save_path : str, optional
  723. If provided, saves the figure to this path (only if a new figure is created).
  724. text : str, optional
  725. Additional annotation text to display in the top-left corner of the plot.
  726. loc : float, optional (default = 0.05)
  727. Horizontal position (axis fraction) of the annotation text.
  728. """
  729. # Sort data by x-axis values
  730. data = data.sort_values([x_column])
  731. # Get count of each timepoint
  732. count_samples = data[x_column].dropna().value_counts().sort_index()
  733. x_labels = [f"{tp} (n={count_samples[tp]})" for tp in count_samples.index]
  734. # If ax is not provided, create a new figure and axis
  735. created_fig = False
  736. if ax is None:
  737. fig, ax = plt.subplots(figsize=(8, 6))
  738. created_fig = True # Track if we created a new figure
  739. else:
  740. fontsize_title = 16 # Reduce title size for subplots
  741. # Create violin plot
  742. sns.violinplot(x=data[x_column], y=data[y_column], palette=palette, ax=ax, cut=0)
  743. # Overlay boxplot
  744. sns.boxplot(x=data[x_column], y=data[y_column], saturation=0.3, width=0.4, palette=palette,
  745. boxprops={'zorder': 2}, ax=ax)
  746. # Set titles and labels
  747. ax.set_title(title, fontsize=fontsize_title)
  748. ax.set_xlabel(None)
  749. ax.set_ylabel(y_column_name, fontsize=fontsize_axes)
  750. ax.grid(True)
  751. # Rotate x-axis labels if using subplots
  752. rotation_angle = 45 if not created_fig else 0
  753. ax.set_xticklabels(x_labels, fontsize=fontsize_axes, rotation=rotation_angle, ha="right" if rotation_angle else "center")
  754. # Add optional text annotation
  755. if text is not None:
  756. ax.text(loc, 0.95, text, transform=ax.transAxes, fontsize=10, verticalalignment='top',
  757. horizontalalignment='left', bbox=dict(facecolor='white', alpha=0.5))
  758. # Save the figure if path is provided
  759. if save_path and created_fig:
  760. plt.savefig(save_path, dpi=300, bbox_inches='tight')
  761. # If a new figure was created, show it
  762. if created_fig:
  763. plt.show()
  764. return ax # Return axis for further modifications if needed
  765. def scatterplot(data, x_column, y_column, timepoint_column, title, y_column_name, x_column_name, palette,
  766. ax=None, fontsize_title=16, fontsize_axes=14, save_path=None,
  767. loc_position = 'lower right', log=False):
  768. """
  769. Creates a scatter plot with regression lines and confidence intervals for different subjects or timepoints. Also annotates the legend with sample sizes.
  770. Parameters
  771. ----------
  772. data : pandas.DataFrame
  773. The dataset containing values for the x-axis, y-axis, and groups defined by `timepoint_column`.
  774. x_column : str
  775. Name of the column in `data` to be plotted on the x-axis.
  776. y_column : str
  777. Name of the column in `data` to be plotted on the y-axis.
  778. timepoint_column : str
  779. Name of the column that defines groups (e.g., timepoints) used for coloring and legend entries.
  780. title : str
  781. Title of the plot.
  782. y_column_name : str
  783. Display name (label) for the y-axis.
  784. x_column_name : str
  785. Display name (label) for the x-axis.
  786. palette : list or dict
  787. Color palette used for different timepoints. Should have at least as many entries as unique groups in `timepoint_column`.
  788. ax : matplotlib.axes.Axes, optional
  789. A Matplotlib Axes object to draw the plot on. If None, a new figure and axes are created.
  790. fontsize_title : int, optional (default=16)
  791. Font size for the plot title.
  792. fontsize_axes : int, optional (default=14)
  793. Font size for the axis labels.
  794. save_path : str or None, optional
  795. If provided, the figure will be saved to this file path.
  796. loc_position : str or None, optional (default='lower right')
  797. Position of the legend. If None, the legend is omitted.
  798. """
  799. data = data.sort_values([timepoint_column])
  800. if ax is None:
  801. fig, ax = plt.subplots(figsize=(5, 6))
  802. # Scatter plot
  803. sns.scatterplot(data=data, x=x_column, y=y_column, hue=timepoint_column, palette=palette, ax=ax)
  804. # Add regression lines with confidence intervals for each timepoint
  805. legend = []
  806. updated_handles = [] # Store legend handles
  807. i=0
  808. for timepoint, subset in data.groupby(timepoint_column):
  809. n_samples = len(subset)
  810. legend.append(f"{timepoint} (n={n_samples})") # Add sample size to label
  811. updated_handles.append(mlines.Line2D([], [], color=palette[i], marker='o', linestyle='None', markersize=8))
  812. i+=1
  813. sns.regplot(data=data, x=x_column, y=y_column, scatter=False, color='black', ci=95, ax=ax)
  814. # Labels and title
  815. ax.set_ylabel(y_column_name, fontsize=fontsize_axes)
  816. ax.set_title(title, fontsize=fontsize_title)
  817. ax.grid(True)
  818. ax.set_xlabel(x_column_name, fontsize=fontsize_axes)
  819. # Adjust legend
  820. ax.legend_.remove()
  821. if loc_position != None:
  822. loc_position = loc_position
  823. ax.legend(updated_handles, legend, title='', loc=loc_position, fontsize=12, title_fontsize=13)
  824. # Save figure if a path is provided
  825. if save_path:
  826. plt.savefig(save_path, bbox_inches='tight', dpi=300)
  827. return ax
  828. def scatterplot_age(data, x_column, y_column, timepoint_column, title, y_column_name, x_column_name, palette,
  829. ax=None, fontsize_title=16, fontsize_axes=14, save_path=None, loc_position='lower right'):
  830. """
  831. Creates a scatter plot with regression lines and confidence intervals for each group in timepoint_column. Also annotates the legend with sample sizes.
  832. Parameters
  833. ----------
  834. data : pandas.DataFrame
  835. The dataset containing values for the x-axis, y-axis, and groups defined by `timepoint_column`.
  836. x_column : str
  837. Name of the column in `data` to be plotted on the x-axis.
  838. y_column : str
  839. Name of the column in `data` to be plotted on the y-axis.
  840. timepoint_column : str
  841. Name of the column that defines groups (e.g., timepoints) used for coloring and legend entries.
  842. title : str
  843. Title of the plot.
  844. y_column_name : str
  845. Display name (label) for the y-axis.
  846. x_column_name : str
  847. Display name (label) for the x-axis.
  848. palette : list or dict
  849. Color palette used for different timepoints. Should have at least as many entries as unique groups in `timepoint_column`.
  850. ax : matplotlib.axes.Axes, optional
  851. A Matplotlib Axes object to draw the plot on. If None, a new figure and axes are created.
  852. fontsize_title : int, optional (default=16)
  853. Font size for the plot title.
  854. fontsize_axes : int, optional (default=14)
  855. Font size for the axis labels.
  856. save_path : str or None, optional
  857. If provided, the figure will be saved to this file path.
  858. loc_position : str or None, optional (default='lower right')
  859. Position of the legend. If None, the legend is omitted.
  860. """
  861. data = data.sort_values([timepoint_column])
  862. if ax is None:
  863. fig, ax = plt.subplots(figsize=(8, 6))
  864. # Scatter plot
  865. sns.scatterplot(data=data, x=x_column, y=y_column, hue=timepoint_column, palette=palette, ax=ax)
  866. # Regression lines per timepoint
  867. legend = []
  868. updated_handles = []
  869. for i, (timepoint, subset) in enumerate(data.groupby(timepoint_column)):
  870. n_samples = len(subset)
  871. legend.append(f"{timepoint} (n={n_samples})")
  872. updated_handles.append(mlines.Line2D([], [], color=palette[i], marker='o', linestyle='None', markersize=8))
  873. sns.regplot(
  874. data=subset,
  875. x=x_column,
  876. y=y_column,
  877. scatter=False,
  878. color=palette[i],
  879. ci=95,
  880. ax=ax
  881. )
  882. # Labels and title
  883. ax.set_xlabel(x_column_name, fontsize=fontsize_axes)
  884. ax.set_ylabel(y_column_name, fontsize=fontsize_axes)
  885. ax.set_title(title, fontsize=fontsize_title)
  886. ax.grid(True)
  887. # Adjust legend
  888. ax.legend_.remove()
  889. if loc_position is not None:
  890. ax.legend(updated_handles, legend, title='', loc=loc_position, fontsize=12, title_fontsize=13)
  891. # Save figure if a path is provided
  892. if save_path:
  893. plt.savefig(save_path, bbox_inches='tight', dpi=300)
  894. return ax
  895. def plot_estimates(models, titles, x_pos, save_path):
  896. """
  897. Creates a multi-panel forest plot for rhythmicity models.
  898. Parameters:
  899. -----------
  900. models : list
  901. List of fitted model objects.
  902. titles : list of str
  903. Titles for each subplot.
  904. x_pos : list of float
  905. X-axis positions for the p-value annotations in each subplot.
  906. figures_path : str
  907. Directory path where the PDF will be saved.
  908. """
  909. predictors = models[0].params.drop(['Intercept', 'Group Var'], errors='ignore').rename(index=name_mapping).index
  910. fig, axes = plt.subplots(1, len(models), figsize=(22, len(predictors)), sharey=True)
  911. # Ensure axes is iterable if only one model is provided
  912. if len(models) == 1:
  913. axes = [axes]
  914. for i, (model, ax, title) in enumerate(zip(models, axes, titles)):
  915. params = model.params.drop(['Intercept', 'Group Var'], errors='ignore').rename(index=name_mapping)
  916. conf = model.conf_int().drop(['Intercept', 'Group Var'], errors='ignore').rename(index=name_mapping)
  917. p_values = model.pvalues.drop(['Intercept', 'Group Var'], errors='ignore').rename(index=name_mapping)
  918. # Align predictors to a consistent order
  919. params = params.reindex(predictors)
  920. conf = conf.reindex(predictors)
  921. p_values = p_values.reindex(predictors)
  922. # Compute error bars
  923. lower = params - conf[0]
  924. upper = conf[1] - params
  925. # Plot
  926. ax.errorbar(params.values,
  927. params.index,
  928. xerr=[lower.values, upper.values],
  929. fmt='o',
  930. color='black',
  931. ecolor='gray',
  932. capsize=3)
  933. ax.axvline(x=0, color='blue', linestyle='--')
  934. ax.set_title(title, fontsize=16)
  935. ax.set_xlabel("β Estimate", fontsize=14)
  936. ax.set_ylabel("")
  937. ax.set_yticklabels([])
  938. ax.grid(axis='y')
  939. ax.set_ylim(-0.5, len(predictors) - 0.5)
  940. for tick in ax.get_yticklabels():
  941. if tick.get_text() in bold_labels:
  942. tick.set_fontweight('bold')
  943. # Add p-values as text annotations next to the points
  944. pos = x_pos[i]
  945. for j, (coef, p_val) in enumerate(zip(params.values, p_values.values)):
  946. if np.isnan(p_val):
  947. label = "p = nan"
  948. else:
  949. label = f"p = {p_val:.3f}" if p_val >= 0.001 else "p < 0.001"
  950. ax.text(pos, j + 0.1, label, verticalalignment='center', fontsize=10)
  951. # Label only the first axis
  952. axes[0].set_yticks(range(len(predictors)))
  953. axes[0].set_yticklabels(predictors, fontsize=14)
  954. axes[0].set_ylabel('Predictors', fontsize=14)
  955. plt.savefig(f"{save_path}", dpi=300, bbox_inches='tight')
  956. plt.show()
  957. def plot_estimates_vertically(models, titles, save_path):
  958. """
  959. Creates a multi-panel forest plot for rhythmicity models.
  960. Parameters:
  961. -----------
  962. models : list
  963. List of fitted model objects.
  964. titles : list of str
  965. Titles for each subplot.
  966. save_path : str
  967. Directory path where the PDF will be saved.
  968. """
  969. n_models = len(models)
  970. if n_models <= 2:
  971. x_size = 5
  972. else:
  973. x_size = 12
  974. fig, axes = plt.subplots(n_models, 1, figsize=(x_size, 5 * n_models), sharex=True)
  975. # Ensure axes is iterable if only one model is provided
  976. if n_models == 1:
  977. axes = [axes]
  978. x_pos = 50
  979. else:
  980. x_pos = 0.3
  981. for i, (model, ax, title) in enumerate(zip(models, axes, titles)):
  982. params = model.params.drop(['Intercept', 'Group Var'], errors='ignore').rename(index=name_mapping)
  983. conf = model.conf_int().drop(['Intercept', 'Group Var'], errors='ignore').rename(index=name_mapping)
  984. p_values = model.pvalues.drop(['Intercept', 'Group Var'], errors='ignore').rename(index=name_mapping)
  985. # Compute error bars
  986. lower = params - conf[0]
  987. upper = conf[1] - params
  988. # Plot
  989. ax.errorbar(params.values,
  990. params.index,
  991. xerr=[lower.values, upper.values],
  992. fmt='o',
  993. color='black',
  994. ecolor='gray',
  995. capsize=3)
  996. ax.axvline(x=0, color='blue', linestyle='--')
  997. ax.set_title(title, fontsize=16, pad=10)
  998. ax.set_xlabel("β Estimate", fontsize=14)
  999. ax.set_ylabel("Predictors", fontsize=14)
  1000. ax.grid(axis='y')
  1001. # Show predictor names for each model
  1002. ax.set_yticks(range(len(params.index)))
  1003. ax.set_yticklabels(params.index, fontsize=12)
  1004. ax.set_ylim(-0.5, len(params.index) - 0.5)
  1005. for tick in ax.get_yticklabels():
  1006. if tick.get_text() in bold_labels:
  1007. tick.set_fontweight('bold')
  1008. # Add p-values as text annotations next to the points
  1009. for j, (coef, p_val) in enumerate(zip(params.values, p_values.values)):
  1010. ax.text(x_pos, j + 0.2, f"p = {p_val:.3f}" if p_val >= 0.001 else "p < 0.001",
  1011. verticalalignment='center', fontsize=10)
  1012. # Adjust spacing
  1013. plt.subplots_adjust(hspace=0.5)
  1014. # Save and show using the passed save_path
  1015. plt.savefig(save_path, dpi=300, bbox_inches='tight')
  1016. plt.show()
  1017. def prepare_features(feature_table, data_analysis, data_timepoint_sleep, column='babySQUID'):
  1018. """
  1019. Prepare feature and label arrays for model training by merging feature data,
  1020. metadata, and target sleep-quality measurements.
  1021. This function performs the following steps:
  1022. 1. Merges `feature_table` with `data_analysis` to add `infant_id` and `timepoint`
  1023. based on their shared index.
  1024. 2. Groups features by `infant_id` and `timepoint` and computes the mean for each group.
  1025. 3. Merges the aggregated features with sleep-quality data (`data_timepoint_sleep`)
  1026. using the specified target column.
  1027. 4. Extracts feature matrix `X`, target vector `y`, and group labels for grouped
  1028. cross-validation.
  1029. Parameters
  1030. ----------
  1031. feature_table : pandas.DataFrame
  1032. A dataframe containing extracted features with an index matching that of
  1033. `data_analysis`.
  1034. data_analysis : pandas.DataFrame
  1035. Contains at least the columns `infant_id` and `timepoint`, aligned with
  1036. `feature_table` via index.
  1037. data_timepoint_sleep : pandas.DataFrame
  1038. Table containing sleep-related outcomes, including the specified `column`
  1039. (default: 'babySQUID'), along with `infant_id` and `timepoint`.
  1040. column : str, default='babySQUID'
  1041. The name of the target column to extract from `data_timepoint_sleep`.
  1042. """
  1043. # Merge feature_table with data_analysis on index and specified columns
  1044. feature_table_temp = feature_table.merge(
  1045. data_analysis[['infant_id', 'timepoint']],
  1046. left_index=True,
  1047. right_index=True
  1048. )
  1049. # Group by infant_id and timepoint, then compute mean
  1050. feature_table_mean = feature_table_temp.groupby(['infant_id', 'timepoint']).mean()
  1051. # Merge with sleep quality data
  1052. feature_table_all = feature_table_mean.merge(
  1053. data_timepoint_sleep[['infant_id', 'timepoint', column]],
  1054. left_index=True,
  1055. right_on=['infant_id', 'timepoint'],
  1056. how='inner'
  1057. )
  1058. # Set and reset index
  1059. feature_table_all.set_index(['infant_id', 'timepoint'], inplace=True)
  1060. feature_table_all.reset_index(inplace=True)
  1061. # Extract features and labels
  1062. features = feature_table_all.drop(columns=[column, 'infant_id', 'timepoint'])
  1063. X = features.values
  1064. y = feature_table_all[column].values
  1065. groups = feature_table_all['infant_id']
  1066. return X, y, groups, features
  1067. def evaluate_model(X, y, groups, train_test_split, task_type="regression", n_splits=5, test_size=None):
  1068. """
  1069. Evaluate a machine learning model using grouped cross-validation.
  1070. This function supports both regression and classification tasks and allows for
  1071. multiple grouped splitting strategies (GroupShuffleSplit, GroupKFold,
  1072. LeaveOneGroupOut). It trains a Random Forest model on each split, computes the
  1073. appropriate evaluation metrics, and returns the best-performing model based on
  1074. R² (regression) or accuracy (classification).
  1075. Parameters
  1076. ----------
  1077. X : array-like of shape (n_samples, n_features)
  1078. Feature matrix.
  1079. y : array-like of shape (n_samples,)
  1080. Target vector.
  1081. groups : array-like of shape (n_samples,)
  1082. Group labels used for ensuring train/test splits respect group boundaries.
  1083. train_test_split : {"GroupShuffleSplit", "GroupKFold", "LeaveOneGroupOut"}
  1084. The cross-validation strategy to apply.
  1085. task_type : {"regression", "classification"}, default="regression"
  1086. Determines which model and metrics to use.
  1087. n_splits : int, default=5
  1088. Number of folds or splits (ignored for LeaveOneGroupOut).
  1089. test_size : float, optional
  1090. Test set proportion for GroupShuffleSplit. Ignored for other strategies.
  1091. """
  1092. # Select model based on task
  1093. if task_type == "regression":
  1094. model = RandomForestRegressor(n_estimators=500, random_state=42)
  1095. elif task_type == "classification":
  1096. model = RandomForestClassifier(n_estimators=500, random_state=42)
  1097. else:
  1098. raise ValueError(f"Unsupported task type: {task_type}")
  1099. # Choose splitting strategy
  1100. match train_test_split:
  1101. case 'GroupShuffleSplit':
  1102. random_splits = GroupShuffleSplit(n_splits=n_splits, test_size=test_size, random_state=42)
  1103. case 'GroupKFold':
  1104. random_splits = GroupKFold(n_splits=n_splits)
  1105. case 'LeaveOneGroupOut':
  1106. random_splits = LeaveOneGroupOut()
  1107. case _:
  1108. raise ValueError(f"Unsupported split type: {train_test_split}")
  1109. # Score containers
  1110. r2_scores = []
  1111. mse_scores = []
  1112. mae_scores = []
  1113. acc_scores = []
  1114. f1_scores = []
  1115. best_score = -np.inf
  1116. best_model = None
  1117. best_y_pred = None
  1118. best_y_test = None
  1119. best_X_test = None
  1120. for i, (train_index, test_index) in enumerate(random_splits.split(X, y, groups)):
  1121. X_train, X_test = X[train_index], X[test_index]
  1122. y_train, y_test = y[train_index], y[test_index]
  1123. model.fit(X_train, y_train)
  1124. y_pred = model.predict(X_test)
  1125. if task_type == "regression":
  1126. if len(y_test) < 2:
  1127. r2 = np.nan
  1128. else:
  1129. r2 = r2_score(y_test, y_pred)
  1130. r2_scores.append(r2)
  1131. mse = mean_squared_error(y_test, y_pred)
  1132. mae = mean_absolute_error(y_test, y_pred)
  1133. mse_scores.append(mse)
  1134. mae_scores.append(mae)
  1135. current_score = r2 if not np.isnan(r2) else -np.inf
  1136. elif task_type == "classification":
  1137. acc = accuracy_score(y_test, y_pred)
  1138. f1 = f1_score(y_test, y_pred, average='weighted')
  1139. acc_scores.append(acc)
  1140. f1_scores.append(f1)
  1141. current_score = acc # Or use f1
  1142. # Save best model
  1143. if current_score > best_score:
  1144. best_score = current_score
  1145. best_model = model
  1146. best_y_pred = y_pred
  1147. best_y_test = y_test
  1148. best_X_test = X_test
  1149. # Summary
  1150. print("\n--- Cross-Validation Summary ---")
  1151. if task_type == "regression":
  1152. avg_r2 = np.nanmean(r2_scores)
  1153. avg_mse = np.mean(mse_scores)
  1154. avg_mae = np.mean(mae_scores)
  1155. print(f"Average R²: {avg_r2:.3f}")
  1156. print(f"Average MSE: {avg_mse:.3f}")
  1157. print(f"Average MAE: {avg_mae:.3f}")
  1158. else:
  1159. avg_acc = np.mean(acc_scores)
  1160. std_acc = np.std(acc_scores)
  1161. avg_f1 = np.mean(f1_scores)
  1162. std_f1 = np.std(f1_scores)
  1163. print(f"Average Accuracy: {avg_acc:.3f} (±{std_acc:.4f})")
  1164. print(f"Average F1 Score (weighted): {avg_f1:.3f} (±{std_f1:.4f})\n")
  1165. return best_model, best_y_pred, best_y_test, best_X_test

functions_script.py at commit bbecc5f, no license · at the source

Overview

Authors: Fannie Kerff1, Christophe Mühlematter2, Anja Adamov1, Deborah Fast2, Serafina Plüss1, Petra Zimmermann3,4,5, Salome Kurth2,6, Nicholas A Bokulich1
  1. Department of Health Sciences and Technology, ETH Zurich, Zurich, Switzerland
  2. Department of Psychology, University of Fribourg, Fribourg, Switzerland
  3. Faculty of Health Science and Medicine, University of Lucerne, Lucerne, Switzerland
  4. Department of Paediatrics, Children's Hospital of Central Switzerland, Lucerne, Switzerland
  5. Department of Community Health, Faculty of Science and Medicine, University of Fribourg, Fribourg, Switzerland
  6. Department of Pulmonology, University Hospital Zurich, Zurich, Switzerland
Institutions: ETH Zurich (Switzerland); University of Fribourg (Switzerland); University of Lucerne (Switzerland); University Hospital Zurich (Switzerland)
Journal: Gut microbes reports, volume 3, issue 1, article 2649096
Dates: received 12 August 2025; accepted 17 March 2026; published online 29 March 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1080/29933935.2026.2649096 · PMID 41924305 · PMCID PMC13037436 · OpenAlex W7143401029
Open access: diamond, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: human (organism), developmental (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions, Machine learning
Keywords: Gut microbiota, infant sleep, microbiota–gut–brain axis, melatonin, diurnal rhythmicity
Topic: Neuroscience of respiration and sleep (Endocrine and Autonomic Systems, Neuroscience), according to OpenAlex
Funding: Swiss National Science Foundation (181279)
Citations: cited by 2 papers (Europe PMC); 73 references in the paper

Abstract

Emerging evidence supports a bidirectional relationship between the gut microbiome and sleep, which is partly mediated by the microbiota‒gut‒brain axis. Infancy is a critical window for the establishment of both the gut microbiome and sleep regulation, which we hypothesize to be linked across both short (diurnal) and long (monthly) time scales. In this longitudinal study, we investigated associations between gut microbiota development and sleep patterns in 20 infants at 2, 4, and 6 months of age (n = 163 samples). Infants were continuously monitored across 48-h sampling periods. The gut microbiota profiles were characterized using 16S rRNA gene sequencing; gut melatonin concentrations were measured; sleep data were collected via wearable actimetry, 24-h parent-reported sleep diaries, and the Brief Infant Sleep Questionnaire; and parenting style and behavioral development were assessed. In some infants, bacterial diversity followed diurnal rhythmic patterns. While bacterial rhythmicity was not significantly associated with sleep rhythmicity, infants with higher microbial alpha diversity showed more robust sleep patterns. Infant age emerged as the strongest predictor of gut microbial diversity and melatonin levels. Our findings suggest that gut microbial establishment may support the maturation of sleep‒wake rhythms in early infancy. Further research is needed to elucidate mechanistic roles of the gut microbiome in sleep development.

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

Repository

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

bokulich-publications/microbiome-sleep-rhythmicity

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: bbecc5f41408406edcd5b4d3e7864856c153c18a, 15 March 2026
Languages: Shell (19), Jupyter (9), Python (1)
Size: 43 files, 29 scripts
Software Heritage: not archived
Found in: “Data availability statement”
Holds: README, environment (environment.yml), 9 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: Matplotlib (9 files), pandas (5 files), statsmodels (5 files), NumPy (3 files), scikit-learn (2 files), SciPy (2 files), seaborn (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
30 files

The paper's code and data availability statement is in the Data section.

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:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 29 scripts, each with its path and the digest of its content;
  • 16 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

No dataset and no data link were found in the paper.

Data availability statement

Data and code availability

Newly generated sequencing data (16S rRNA) from stool samples in this study are publicly available in the NCBI Sequence Read Archive (SRA) under accession code PRJEB104111.

All original code supporting the findings of this study are publicly available on GitHub at: https://github.com/bokulich-publications/microbiome-sleep-rhythmicity

Any additional information required to reanalyze the data reported in this paper is available from the corresponding authors upon request.

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

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 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 8 authors, 5 keywords, 1 funder, 70 references.

Cite

This paper

Kerff, F., Mühlematter, C., Adamov, A., Fast, D., Plüss, S., Zimmermann, P., Kurth, S., & Bokulich, N. A. (2026). The gut microbiota and sleep in infants: a focus on diurnal rhythmicity patterns. Gut microbes reports, 3(1), 2649096. https://doi.org/10.1080/29933935.2026.2649096

BibTeX

@article{kerff2026gut,
author = {Kerff, Fannie and Mühlematter, Christophe and Adamov, Anja and Fast, Deborah and Plüss, Serafina and Zimmermann, Petra and Kurth, Salome and Bokulich, Nicholas A},
title = {{The gut microbiota and sleep in infants: a focus on diurnal rhythmicity patterns}},
journal = {Gut microbes reports},
year = {2026},
month = mar,
volume = {3},
number = {1},
pages = {2649096},
publisher = {Taylor \& Francis},
issn = {2993-3935},
doi = {10.1080/29933935.2026.2649096},
url = {https://doi.org/10.1080/29933935.2026.2649096},
pmid = {41924305},
pmcid = {PMC13037436}
}

RIS

TY - JOUR
AU - Kerff, Fannie
AU - Mühlematter, Christophe
AU - Adamov, Anja
AU - Fast, Deborah
AU - Plüss, Serafina
AU - Zimmermann, Petra
AU - Kurth, Salome
AU - Bokulich, Nicholas A
TI - The gut microbiota and sleep in infants: a focus on diurnal rhythmicity patterns
T2 - Gut microbes reports
J2 - Gut Microbes Rep
PY - 2026
DA - 2026/03/29
VL - 3
IS - 1
SP - 2649096
SN - 2993-3935
PB - Taylor & Francis
DO - 10.1080/29933935.2026.2649096
UR - https://doi.org/10.1080/29933935.2026.2649096
LA - en
ER -

CSL-JSON

{
"id": "10.1080/29933935.2026.2649096",
"type": "article-journal",
"title": "The gut microbiota and sleep in infants: a focus on diurnal rhythmicity patterns",
"container-title": "Gut microbes reports",
"author": [
{
"family": "Kerff",
"given": "Fannie"
},
{
"family": "Mühlematter",
"given": "Christophe"
},
{
"family": "Adamov",
"given": "Anja"
},
{
"family": "Fast",
"given": "Deborah"
},
{
"family": "Plüss",
"given": "Serafina"
},
{
"family": "Zimmermann",
"given": "Petra"
},
{
"family": "Kurth",
"given": "Salome"
},
{
"family": "Bokulich",
"given": "Nicholas A"
}
],
"container-title-short": "Gut Microbes Rep",
"volume": "3",
"issue": "1",
"page": "2649096",
"DOI": "10.1080/29933935.2026.2649096",
"PMID": "41924305",
"PMCID": "PMC13037436",
"ISSN": "2993-3935",
"publisher": "Taylor & Francis",
"URL": "https://doi.org/10.1080/29933935.2026.2649096",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
29
]
]
}
}

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.1101/2025.07.11.664337
The gut microbiota and sleep in infants: a focus on diurnal rhythmicity patterns
Journal: bioRxiv (preprint)
In common: developmental, 51 references, 4 authors
[2] doi:10.1371/journal.pone.0345651 [code]
Non-concussive head impacts sustained during American football correlate with changes in gut microbiome diversity and composition.
Journal: PloS one
In common: statsmodels, seaborn, scikit-learn, 4 other tools, 5 references
[3] doi:10.1038/s44323-026-00071-7 [code]
Tracing infant sleep neurophysiology longitudinally from 3 to 6 months: EEG insights into brain development.
Journal: Npj biological timing and sleep
In common: developmental, 3 references, author Salome Kurth
[4] doi:10.1186/s40168-025-02095-w [code]
Distinct gut microbiome characteristics and dynamics in patients with Parkinson's disease based on the presence of premotor rapid-eye movement sleep behavior disorders.
Journal: Microbiome
In common: scikit-learn, SciPy, Matplotlib, 1 other tool, 6 references
[5] doi:10.1016/j.xcrm.2024.101845 [code]
The neonatal gut microbiota: A role in the encephalopathy of prematurity.
Journal: Cell reports. Medicine
In common: developmental, 7 references
[6] doi:10.1126/sciadv.adj0400 [code]
A digital twin of the infant microbiome to predict neurodevelopmental deficits.
Journal: Science advances
In common: statsmodels, seaborn, scikit-learn, 4 other tools, developmental, 3 references
[7] doi:10.3389/fnut.2026.1776896
Maternal docosahexaenoic acid supplementation shapes offspring gut microbiota to modulate the gut-brain axis in a sow-piglet model.
Journal: Frontiers in nutrition
In common: developmental, 6 references
[8] doi:10.1186/s13054-023-04531-5 [code]
Modifications of lung microbiota structure in traumatic brain injury ventilated patients according to time and enteral feeding formulas: a prospective randomized study.
Journal: Critical care (London, England)
In common: statsmodels, seaborn, pandas, 3 other tools, 3 references
[9] doi:10.1038/s41598-023-44676-5
Superior performance of biofilm versus planktonic Limosilactobacillus reuteri in protection of the intestines and brain in a piglet model of necrotizing enterocolitis.
Journal: Scientific reports
In common: developmental, 6 references
[10] doi:10.3389/fnins.2023.1212791
Physiological and cognitive changes after treatments of cyclophosphamide, methotrexate, and fluorouracil: implications of the gut microbiome and depressive-like behavior.
Journal: Frontiers in neuroscience
In common: 6 references

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.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1080/29933935.2026.2649096.svg)](https://openscicode.org/badge/doi/10.1080/29933935.2026.2649096)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1080/29933935.2026.2649096.svg
   :target: https://openscicode.org/badge/doi/10.1080/29933935.2026.2649096
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1080/29933935.2026.2649096"><img src="https://openscicode.org/badge/doi/10.1080/29933935.2026.2649096.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge 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: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

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