pythonflex 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {pythonflex-0.3.2 → pythonflex-0.3.3}/PKG-INFO +1 -1
  2. {pythonflex-0.3.2 → pythonflex-0.3.3}/pyproject.toml +1 -1
  3. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/__init__.py +2 -2
  4. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/analysis.py +82 -1
  5. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/examples/basic_usage.py +11 -10
  6. pythonflex-0.3.3/src/pythonflex/examples/manuscript.py +111 -0
  7. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/plotting.py +128 -7
  8. {pythonflex-0.3.2 → pythonflex-0.3.3}/.gitignore +0 -0
  9. {pythonflex-0.3.2 → pythonflex-0.3.3}/.python-version +0 -0
  10. {pythonflex-0.3.2 → pythonflex-0.3.3}/README.md +0 -0
  11. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/__init__.py +0 -0
  12. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/dataset/__init__.py +0 -0
  13. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/dataset/liver_cell_lines_500_genes.csv +0 -0
  14. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/dataset/melanoma_cell_lines_500_genes.csv +0 -0
  15. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/dataset/neuroblastoma_cell_lines_500_genes.csv +0 -0
  16. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/gold_standard/CORUM.parquet +0 -0
  17. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/gold_standard/GOBP.parquet +0 -0
  18. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/gold_standard/PATHWAY.parquet +0 -0
  19. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/gold_standard/__init__.py +0 -0
  20. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/gold_standard/corum.csv +0 -0
  21. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/gold_standard/gobp.csv +0 -0
  22. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/data/gold_standard/pathway.csv +0 -0
  23. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/logging_config.py +0 -0
  24. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/preprocessing.py +0 -0
  25. {pythonflex-0.3.2 → pythonflex-0.3.3}/src/pythonflex/utils.py +0 -0
  26. {pythonflex-0.3.2 → pythonflex-0.3.3}/todo.txt +0 -0
  27. {pythonflex-0.3.2 → pythonflex-0.3.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonflex
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: pythonFLEX is a benchmarking toolkit for evaluating CRISPR screen results against biological gold standards. The toolkit computes gene-level and complex-level performance metrics, helping researchers systematically assess the biological relevance and resolution of their CRISPR screening data.
5
5
  Author-email: Yasir Demirtaş <tyasird@hotmail.com>
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pythonflex"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "pythonFLEX is a benchmarking toolkit for evaluating CRISPR screen results against biological gold standards. The toolkit computes gene-level and complex-level performance metrics, helping researchers systematically assess the biological relevance and resolution of their CRISPR screening data."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -5,7 +5,7 @@ from .analysis import initialize, pra, pra_percomplex, fast_corr, perform_corr,
5
5
  from .plotting import (
6
6
  adjust_text_positions, plot_precision_recall_curve, plot_aggregated_pra, plot_iqr_pra, plot_all_runs_pra, plot_percomplex_scatter,
7
7
  plot_percomplex_scatter_bysize, plot_complex_contributions, plot_significant_complexes, plot_auc_scores,
8
- plot_mpr_tp, plot_mpr_complexes, plot_mpr_tp_multi, plot_mpr_complexes_multi
8
+ plot_mpr_tp, plot_mpr_complexes, plot_mpr_tp_multi, plot_mpr_complexes_multi, plot_mpr_complexes_auc_scores
9
9
  )
10
10
 
11
11
  __all__ = [ "log", "get_example_data_path", "fast_corr",
@@ -14,7 +14,7 @@ __all__ = [ "log", "get_example_data_path", "fast_corr",
14
14
  "perform_corr", "is_symmetric", "binary", "has_mirror_of_first_pair", "convert_full_to_half_matrix",
15
15
  "drop_mirror_pairs", "quick_sort", "complex_contributions", "adjust_text_positions", "plot_precision_recall_curve",
16
16
  "plot_aggregated_pra", "plot_iqr_pra", "plot_all_runs_pra", "plot_percomplex_scatter", "plot_percomplex_scatter_bysize", "plot_complex_contributions",
17
- "plot_significant_complexes", "plot_auc_scores", "save_results_to_csv", "update_matploblib_config",
17
+ "plot_significant_complexes", "plot_auc_scores", "plot_mpr_complexes_auc_scores", "save_results_to_csv", "update_matploblib_config",
18
18
  "mpr_prepare", "plot_mpr_tp", "plot_mpr_complexes",
19
19
  "plot_mpr_tp_multi", "plot_mpr_complexes_multi"
20
20
  ]
@@ -844,7 +844,7 @@ def quick_sort(df, ascending=False):
844
844
  log.done("Pair-wise matrix sorting.")
845
845
  return sorted_df
846
846
 
847
- def save_results_to_csv(categories = ["complex_contributions", "pr_auc", "pra_percomplex"]):
847
+ def save_results_to_csv(categories = ["complex_contributions", "pr_auc", "pra_percomplex", "mpr_complexes_auc"]):
848
848
 
849
849
  config = dload("config") # Load config to get output folder
850
850
  output_folder = Path(config.get("output_folder", "output"))
@@ -856,6 +856,18 @@ def save_results_to_csv(categories = ["complex_contributions", "pr_auc", "pra_pe
856
856
  if data is None:
857
857
  log.warning(f"No data found for category '{category}'. Skipping save.")
858
858
  continue
859
+
860
+ if category == "mpr_complexes_auc" and isinstance(data, dict):
861
+ # Dict[dataset_name -> Dict[filter_key -> auc]]
862
+ try:
863
+ df = pd.DataFrame.from_dict(data, orient="index")
864
+ df.index.name = "Dataset"
865
+ csv_path = output_folder / f"{category}.csv"
866
+ df.to_csv(csv_path, index=True)
867
+ log.info(f"Saved '{category}' to {csv_path}")
868
+ except Exception as e:
869
+ log.warning(f"Failed to convert and save '{category}': {e}")
870
+ continue
859
871
 
860
872
  if category == "pr_auc" and isinstance(data, dict):
861
873
  # Special handling: Convert dict to DataFrame (assuming keys are indices, values are data)
@@ -1312,6 +1324,64 @@ def _mpr_module_coverage(contrib_df, terms, tp_th=1, percent_th=0.1):
1312
1324
  return coverage
1313
1325
 
1314
1326
 
1327
+ def _mpr_complexes_auc(
1328
+ coverage: np.ndarray,
1329
+ precision_cutoffs: np.ndarray,
1330
+ max_complexes: float = 200.0,
1331
+ ) -> float:
1332
+ """Compute AUC for the Fig. 1F-style mPR curve (#complexes vs precision).
1333
+
1334
+ The plot uses:
1335
+ x = #covered complexes (capped at `max_complexes`, shown on a log axis)
1336
+ y = precision cutoff
1337
+
1338
+ We compute a normalized AUC by integrating precision over the *normalized*
1339
+ coverage axis:
1340
+ AUC = \int y \, d(x/max_complexes)
1341
+
1342
+ This yields a score in [0, 1] (or NaN if insufficient data).
1343
+ """
1344
+ cov = np.asarray(coverage, dtype=float)
1345
+ prec = np.asarray(precision_cutoffs, dtype=float)
1346
+
1347
+ if cov.size == 0 or prec.size == 0:
1348
+ return 0.0
1349
+
1350
+ # Match plot_mpr_complexes_multi(): only count cov>0 (log-x cannot show 0)
1351
+ mask = (
1352
+ np.isfinite(cov)
1353
+ & np.isfinite(prec)
1354
+ & (cov > 0)
1355
+ & (cov <= max_complexes)
1356
+ & (prec >= 0)
1357
+ & (prec <= 1.0)
1358
+ )
1359
+ if not np.any(mask):
1360
+ return 0.0
1361
+
1362
+ x_cov = cov[mask]
1363
+ y = prec[mask]
1364
+
1365
+ # x-axis is log-scaled in the plot; normalize so cov=1 -> 0, cov=max_complexes -> 1
1366
+ # (This matches the plot's tick hack where 1 is labeled as "0".)
1367
+ x = np.log10(x_cov) / np.log10(float(max_complexes))
1368
+
1369
+ # Sort by x and collapse duplicate x values by taking max y (upper envelope)
1370
+ order = np.argsort(x)
1371
+ x = x[order]
1372
+ y = y[order]
1373
+
1374
+ x_unique = np.unique(x)
1375
+ if x_unique.size != x.size:
1376
+ y = np.array([float(np.nanmax(y[x == xv])) for xv in x_unique], dtype=float)
1377
+ x = x_unique
1378
+
1379
+ if x.size < 2:
1380
+ return 0.0
1381
+
1382
+ return float(np.trapz(y, x))
1383
+
1384
+
1315
1385
 
1316
1386
 
1317
1387
 
@@ -1379,6 +1449,7 @@ def mpr_prepare(
1379
1449
 
1380
1450
  tp_curves = {}
1381
1451
  coverage_curves = {}
1452
+ complexes_auc = {}
1382
1453
  precision_cutoffs = None
1383
1454
 
1384
1455
  for label, removed in filter_sets.items():
@@ -1393,6 +1464,7 @@ def mpr_prepare(
1393
1464
  "precision": np.array([], dtype=float),
1394
1465
  }
1395
1466
  coverage_curves[label] = np.zeros(0, dtype=float)
1467
+ complexes_auc[label] = float("nan")
1396
1468
  continue
1397
1469
 
1398
1470
  tp_cum = true.cumsum()
@@ -1417,11 +1489,17 @@ def mpr_prepare(
1417
1489
  percent_th=percent_th,
1418
1490
  )
1419
1491
  coverage_curves[label] = cov
1492
+ complexes_auc[label] = _mpr_complexes_auc(
1493
+ cov,
1494
+ precision_cutoffs,
1495
+ max_complexes=200.0,
1496
+ )
1420
1497
 
1421
1498
  mpr_data = {
1422
1499
  "precision_cutoffs": precision_cutoffs,
1423
1500
  "tp_curves": tp_curves,
1424
1501
  "coverage_curves": coverage_curves,
1502
+ "complexes_auc": complexes_auc,
1425
1503
  "filters": {
1426
1504
  "no_mtRibo_ETCI": sorted(mtRibo_ids),
1427
1505
  "no_small_highAUPRC": sorted(small_hi_ids),
@@ -1435,6 +1513,9 @@ def mpr_prepare(
1435
1513
 
1436
1514
  dsave(mpr_data, "mpr", name)
1437
1515
 
1516
+ # Convenience: store AUCs as their own category for easy export / plotting.
1517
+ dsave(complexes_auc, "mpr_complexes_auc", name)
1518
+
1438
1519
 
1439
1520
 
1440
1521
  ### OLD FUNCTIONS
@@ -67,20 +67,21 @@ for name, dataset in data.items():
67
67
 
68
68
  #%%
69
69
  # Generate plots
70
- flex.plot_precision_recall_curve()
71
- flex.plot_auc_scores()
72
- flex.plot_significant_complexes()
73
- flex.plot_percomplex_scatter(n_top=20)
74
- flex.plot_percomplex_scatter_bysize()
75
- flex.plot_complex_contributions()
76
- ##
77
- flex.plot_mpr_tp_multi()
78
- flex.plot_mpr_complexes_multi()
70
+ # flex.plot_precision_recall_curve()
71
+ # flex.plot_auc_scores()
72
+ # flex.plot_significant_complexes()
73
+ # flex.plot_percomplex_scatter(n_top=20)
74
+ # flex.plot_percomplex_scatter_bysize()
75
+ # flex.plot_complex_contributions()
76
+ #%%
77
+ #flex.plot_mpr_tp_multi(show_filters="all")
78
+ flex.plot_mpr_complexes_multi(show_filters="all")
79
79
 
80
80
  #%%
81
81
  # Save results to CSV
82
82
  flex.save_results_to_csv()
83
83
 
84
+
84
85
  # %%
85
- flex.plot_mpr_complexes_multi(show_filters="no_mtRibo_ETCI")
86
+ flex.plot_mpr_complexes_auc_scores("all")
86
87
  # %%
@@ -0,0 +1,111 @@
1
+ """
2
+ Basic usage example of the pythonFLEX package.
3
+ Demonstrates initialization, data loading, analysis, and plotting.
4
+ """
5
+ #%%
6
+ import pythonflex as flex
7
+ import pandas as pd
8
+
9
+ gene_effect = pd.read_csv('C:/Users/yd/Desktop/projects/_datasets/depmap/25Q2/gene_effect.csv', index_col=0)
10
+
11
+ skin = pd.read_csv('C:/Users/yd/Desktop/projects/_datasets/depmap/25Q2/subset/skin_cell_lines.csv', index_col=0)
12
+
13
+ soft = pd.read_csv('C:/Users/yd/Desktop/projects/_datasets/depmap/25Q2/subset/soft_tissue_cell_lines.csv', index_col=0)
14
+
15
+
16
+ cholesky = pd.read_csv('C:/Users/yd/Desktop/projects/_datasets/depmap/25Q2/25Q2_chronos_whitened_Cholesky.csv', index_col=0).T
17
+
18
+ # inputs = {
19
+ # "All Screens": {
20
+ # "path": gene_effect,
21
+ # "sort": "high",
22
+ # "color": "#000000"
23
+ # },
24
+ # "Skin": {
25
+ # "path": skin,
26
+ # "sort": "high",
27
+ # "color": "#FF0000"
28
+ # },
29
+ # "Soft Tissue": {
30
+ # "path": soft,
31
+ # "sort": "high",
32
+ # "color": "#FFFF00"
33
+ # },
34
+ # }
35
+
36
+
37
+ inputs = {
38
+ "DM All Screens": {
39
+ "path": gene_effect,
40
+ "sort": "high",
41
+ "color": "#000000"
42
+ },
43
+ "DM Cholesky Whitening": {
44
+ "path": cholesky,
45
+ "sort": "high",
46
+ "color": "#FF0000"
47
+ },
48
+
49
+ }
50
+
51
+
52
+
53
+
54
+ default_config = {
55
+ "min_genes_in_complex": 2,
56
+ "min_genes_per_complex_analysis": 3,
57
+ "output_folder": "CORUM_DMvsCholesky",
58
+ "gold_standard": "CORUM",
59
+ "color_map": "BuGn",
60
+ "jaccard": False,
61
+ "use_common_genes": False, # Set to False for individual dataset-gold standard intersections
62
+ "plotting": {
63
+ "save_plot": True,
64
+ "output_type": "pdf",
65
+ },
66
+ "preprocessing": {
67
+ "fill_na": True,
68
+ "normalize": False,
69
+ },
70
+ "corr_function": "numpy",
71
+ "logging": {
72
+ "visible_levels": ["DONE"]
73
+ # "PROGRESS", "STARTED", ,"INFO","WARNING"
74
+ }
75
+ }
76
+
77
+ # Initialize logger, config, and output folder
78
+ flex.initialize(default_config)
79
+
80
+ # Load datasets and gold standard terms
81
+ data, _ = flex.load_datasets(inputs)
82
+ terms, genes_in_terms = flex.load_gold_standard()
83
+
84
+ # Run analysis
85
+ for name, dataset in data.items():
86
+ pra = flex.pra(name, dataset, is_corr=False)
87
+ fpc = flex.pra_percomplex(name, dataset, is_corr=False)
88
+ cc = flex.complex_contributions(name)
89
+ flex.mpr_prepare(name)
90
+
91
+
92
+
93
+
94
+ #%%
95
+ # Generate plots
96
+ flex.plot_precision_recall_curve()
97
+ flex.plot_auc_scores()
98
+ flex.plot_significant_complexes()
99
+ flex.plot_percomplex_scatter(n_top=20)
100
+ flex.plot_percomplex_scatter_bysize()
101
+ flex.plot_complex_contributions()
102
+ ##
103
+ #%%
104
+ flex.plot_mpr_tp_multi(show_filters="all")
105
+ flex.plot_mpr_complexes_multi(show_filters="all")
106
+
107
+ # Save results to CSV
108
+ flex.save_results_to_csv()
109
+
110
+ # %%
111
+ # %%
@@ -1220,6 +1220,107 @@ def plot_auc_scores():
1220
1220
  plt.close(fig)
1221
1221
  return pra_dict
1222
1222
 
1223
+
1224
+ def plot_mpr_complexes_auc_scores(filter_key: str = "all"):
1225
+ """Plot AUC scores for the mPR complexes curve (Fig 1F-style).
1226
+
1227
+ Requires `mpr_prepare()` to have been run for each dataset.
1228
+
1229
+ Parameters
1230
+ ----------
1231
+ filter_key : str
1232
+ One of: "all", "no_mtRibo_ETCI", "no_small_highAUPRC".
1233
+
1234
+ Returns
1235
+ -------
1236
+ pd.Series
1237
+ AUC values indexed by dataset name (sorted descending).
1238
+ """
1239
+ config = dload("config")
1240
+ plot_config = config["plotting"]
1241
+ mpr_auc_dict = dload("mpr_complexes_auc")
1242
+ input_colors = dload("input", "colors")
1243
+
1244
+ if input_colors:
1245
+ input_colors = {_sanitize(k): v for k, v in input_colors.items()}
1246
+
1247
+ if not isinstance(mpr_auc_dict, dict) or not mpr_auc_dict:
1248
+ log.warning(
1249
+ "No mPR complexes AUC data found. Run mpr_prepare() first (it stores 'mpr_complexes_auc')."
1250
+ )
1251
+ return pd.Series(dtype=float)
1252
+
1253
+ # Build Series: dataset -> auc
1254
+ auc_by_dataset = {}
1255
+ for dataset, per_filter in mpr_auc_dict.items():
1256
+ if not isinstance(per_filter, dict):
1257
+ continue
1258
+ val = per_filter.get(filter_key)
1259
+ if val is None:
1260
+ continue
1261
+ try:
1262
+ auc_by_dataset[dataset] = float(val)
1263
+ except (TypeError, ValueError):
1264
+ continue
1265
+
1266
+ if not auc_by_dataset:
1267
+ log.warning(
1268
+ f"No mPR complexes AUC scores found for filter '{filter_key}'. Available filters: {list(FILTER_STYLES.keys())}"
1269
+ )
1270
+ return pd.Series(dtype=float)
1271
+
1272
+ s = pd.Series(auc_by_dataset).sort_values(ascending=False)
1273
+ datasets = list(s.index)
1274
+ auc_scores = list(s.values)
1275
+
1276
+ fig, ax = plt.subplots()
1277
+
1278
+ # Color logic (match other bar plots)
1279
+ cmap_name = config.get("color_map", "tab10")
1280
+ try:
1281
+ cmap = get_cmap(cmap_name)
1282
+ except ValueError:
1283
+ cmap = get_cmap("tab10")
1284
+
1285
+ num_datasets = len(datasets)
1286
+ if num_datasets <= 10 and cmap_name == "tab10":
1287
+ default_colors = [cmap(i) for i in range(num_datasets)]
1288
+ else:
1289
+ default_colors = [cmap(float(i) / max(num_datasets - 1, 1)) for i in range(num_datasets)]
1290
+
1291
+ final_colors = []
1292
+ for i, dataset in enumerate(datasets):
1293
+ color = input_colors.get(dataset) if input_colors else None
1294
+ if color is None:
1295
+ color = default_colors[i]
1296
+ final_colors.append(color)
1297
+
1298
+ ax.bar(datasets, auc_scores, color=final_colors, edgecolor="black")
1299
+
1300
+ ymax = max([v for v in auc_scores if np.isfinite(v)], default=0.0)
1301
+ ax.set_ylim(0, ymax + 0.01)
1302
+ ax.set_ylabel("mPR complexes AUC")
1303
+ plt.xticks(rotation=45, ha="right")
1304
+
1305
+ # Styling consistent with other plots
1306
+ ax.grid(visible=False, which="both", axis="both")
1307
+ ax.set_axisbelow(False)
1308
+ ax.spines["top"].set_visible(False)
1309
+ ax.spines["right"].set_visible(False)
1310
+
1311
+ if plot_config.get("save_plot", False):
1312
+ output_type = plot_config.get("output_type", "pdf")
1313
+ output_folder = Path(config["output_folder"])
1314
+ output_folder.mkdir(parents=True, exist_ok=True)
1315
+ output_path = output_folder / f"mpr_complexes_auc_{filter_key}.{output_type}"
1316
+ plt.savefig(output_path, bbox_inches="tight", format=output_type)
1317
+
1318
+ if plot_config.get("show_plot", True):
1319
+ plt.show()
1320
+
1321
+ plt.close(fig)
1322
+ return s
1323
+
1223
1324
  # -----------------------------------------------------------------------------
1224
1325
  # mPR plots (Fig. 1E and Fig. 1F)
1225
1326
  # -----------------------------------------------------------------------------
@@ -1620,6 +1721,8 @@ def plot_mpr_complexes_multi(
1620
1721
  outname=None,
1621
1722
  linewidth=1.8,
1622
1723
  show_filters=("all", "no_mtRibo_ETCI", "no_small_highAUPRC"),
1724
+ show_markers="auto",
1725
+ marker_size=20,
1623
1726
  ):
1624
1727
  """
1625
1728
  Plot module-level PR (#complexes vs precision) for multiple datasets.
@@ -1644,6 +1747,11 @@ def plot_mpr_complexes_multi(
1644
1747
  Line width for all curves
1645
1748
  show_filters : tuple of str
1646
1749
  Which filters to show. Default is all three.
1750
+ show_markers : bool or "auto"
1751
+ If True, draw markers on curves to make short curves visible.
1752
+ If "auto" (default), markers are drawn only for curves with <= 10 points.
1753
+ marker_size : int
1754
+ Scatter marker size (points^2) when markers are shown.
1647
1755
 
1648
1756
  Returns
1649
1757
  -------
@@ -1730,13 +1838,26 @@ def plot_mpr_complexes_multi(
1730
1838
  prec_plot = precision_cutoffs[mask]
1731
1839
 
1732
1840
  style = FILTER_STYLES.get(filter_key, {})
1733
- ax.plot(
1734
- cov_plot,
1735
- prec_plot,
1736
- color=color,
1737
- linestyle=style.get("linestyle", "-"),
1738
- linewidth=linewidth,
1739
- )
1841
+
1842
+ # Decide marker visibility
1843
+ if show_markers == "auto":
1844
+ use_markers = (cov_plot.size <= 10)
1845
+ else:
1846
+ use_markers = bool(show_markers)
1847
+
1848
+ if cov_plot.size == 1:
1849
+ # A single point is effectively invisible as a line; draw a marker.
1850
+ ax.scatter(cov_plot, prec_plot, color=color, s=marker_size, zorder=3)
1851
+ else:
1852
+ ax.plot(
1853
+ cov_plot,
1854
+ prec_plot,
1855
+ color=color,
1856
+ linestyle=style.get("linestyle", "-"),
1857
+ linewidth=linewidth,
1858
+ marker=("o" if use_markers else None),
1859
+ markersize=(3 if use_markers else None),
1860
+ )
1740
1861
 
1741
1862
  # Configure axes
1742
1863
  ax.set_xscale("log")
File without changes
File without changes
File without changes
File without changes
File without changes