pandas-plots 0.12.22__py3-none-any.whl → 0.12.24__py3-none-any.whl

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.
pandas_plots/hlp.py CHANGED
@@ -333,7 +333,7 @@ def show_package_version(
333
333
  "numpy",
334
334
  "duckdb",
335
335
  "pandas-plots",
336
- "connection_helper",
336
+ "connection-helper",
337
337
  ]
338
338
  items = []
339
339
  items.append(f"🐍 {python_version()}")
pandas_plots/pls.py CHANGED
@@ -1016,6 +1016,7 @@ def plot_box(
1016
1016
  lvl3 = height * 0.25
1017
1017
 
1018
1018
  caption = _set_caption(caption)
1019
+ log_str = " (log-scale)" if use_log else ""
1019
1020
  dict = {
1020
1021
  "data_frame": ser,
1021
1022
  "orientation": "h",
@@ -1026,7 +1027,7 @@ def plot_box(
1026
1027
  # 'box':True,
1027
1028
  "log_x": use_log, # * logarithmic scale, axis is always x
1028
1029
  # "notched": True,
1029
- "title": f"{caption}[{ser.name}], n = {n_:_}" if not title else title,
1030
+ "title": f"{caption}[{ser.name}]{log_str}, n = {n_:_}" if not title else title,
1030
1031
  }
1031
1032
 
1032
1033
  fig = px.violin(**{**dict, "box": True}) if violin else px.box(**dict)
@@ -1173,6 +1174,7 @@ def plot_boxes(
1173
1174
  items = df.iloc[:, 0].unique()
1174
1175
 
1175
1176
  caption = _set_caption(caption)
1177
+ log_str = " (log-scale)" if use_log else ""
1176
1178
 
1177
1179
  # * main plot
1178
1180
  fig = px.box(
@@ -1188,7 +1190,7 @@ def plot_boxes(
1188
1190
  log_y=use_log,
1189
1191
  # color_discrete_sequence=px.colors.qualitative.Plotly,
1190
1192
  title=(
1191
- f"{caption}[{df.columns[0]}] on [{df.columns[1]}], n = {len(df):_.0f}"
1193
+ f"{caption}[{df.columns[0]}] by [{df.columns[1]}]{log_str}, n = {len(df):_.0f}"
1192
1194
  if not title
1193
1195
  else title
1194
1196
  ),
@@ -1271,7 +1273,6 @@ def plot_boxes(
1271
1273
 
1272
1274
  return fig
1273
1275
 
1274
-
1275
1276
  def plot_facet_stacked_bars(
1276
1277
  df: pd.DataFrame,
1277
1278
  subplots_per_row: int = 4,
@@ -1292,59 +1293,41 @@ def plot_facet_stacked_bars(
1292
1293
  sort_values_color: bool = False,
1293
1294
  sort_values_facet: bool = False,
1294
1295
  relative: bool = False,
1295
-
1296
- ) -> object:
1297
- """
1298
- Create a grid of stacked bar charts.
1296
+ show_pct: bool = False,
1297
+ ) -> go.Figure:
1299
1298
 
1300
- Args:
1301
- df (pd.DataFrame): DataFrame with 3 or 4 columns.
1302
- subplots_per_row (int): Number of subplots per row.
1303
- top_n_index (int): top N index values to keep.
1304
- top_n_color (int): top N column values to keep.
1305
- top_n_facet (int): top N facet values to keep.
1306
- null_label (str): Label for null values.
1307
- subplot_size (int): Size of each subplot.
1308
- color_palette (str): Name of the color palette.
1309
- caption (str): Optional caption to prepend to the title.
1310
- renderer (Optional[Literal["png", "svg"]]): Renderer for saving the image.
1311
- annotations (bool): Whether to show annotations in the subplots.
1312
- precision (int): Decimal precision for annotations.
1313
- png_path (Optional[Path]): Path to save the image.
1314
- show_other (bool): If True, adds an "<other>" bar for columns not in top_n_color.
1315
- sort_values_index (bool): If True, sorts index by group sum.
1316
- sort_values_color (bool): If True, sorts columns by group sum.
1317
- sort_values_facet (bool): If True, sorts facet by group sum.
1318
- relative (bool): If True, show bars as relative proportions to 100%.
1319
- sort_values (bool): DEPRECATED
1320
-
1321
-
1322
- Returns:
1323
- plot object
1299
+ # --- ENFORCE show_pct RULES ---
1300
+ if not relative:
1301
+ # If bars are absolute, annotations MUST be absolute
1302
+ if show_pct:
1303
+ print("Warning: 'show_pct' cannot be True when 'relative' is False. Setting 'show_pct' to False.")
1304
+ show_pct = False
1305
+ # ------------------------------
1324
1306
 
1325
- Remarks:
1326
- If you need to include facets that have no data, fill up like this beforehand:
1327
- df.loc[len(df)]=[None, None, 12]
1328
- """
1307
+ try:
1308
+ precision = int(precision)
1309
+ except (ValueError, TypeError):
1310
+ print(f"Warning: 'precision' received as {precision} (type: {type(precision)}). Defaulting to 0.")
1311
+ precision = 0
1329
1312
 
1330
- df = df.copy() # Copy the input DataFrame to avoid modifying the original
1313
+ df_copy = df.copy()
1331
1314
 
1332
- if not (df.shape[1] == 3 or df.shape[1] == 4):
1315
+ if not (df_copy.shape[1] == 3 or df_copy.shape[1] == 4):
1333
1316
  raise ValueError("Input DataFrame must have 3 or 4 columns.")
1334
1317
 
1335
- original_column_names = df.columns.tolist()
1336
- original_rows = len(df)
1318
+ original_column_names = df_copy.columns.tolist()
1337
1319
 
1338
- if df.shape[1] == 3:
1339
- df.columns = ["index", "col", "facet"]
1340
- df["value"] = 1
1341
- elif df.shape[1] == 4:
1342
- df.columns = ["index", "col", "facet", "value"]
1343
-
1344
- n = df["value"].sum()
1320
+ if df_copy.shape[1] == 3:
1321
+ df_copy.columns = ["index", "col", "facet"]
1322
+ df_copy["value"] = 1
1323
+ elif df_copy.shape[1] == 4:
1324
+ df_copy.columns = ["index", "col", "facet", "value"]
1345
1325
 
1346
- aggregated_df = aggregate_data(
1347
- df,
1326
+ n = df_copy["value"].sum()
1327
+ original_rows = len(df_copy)
1328
+
1329
+ aggregated_df = aggregate_data( # Assumes aggregate_data is accessible
1330
+ df_copy,
1348
1331
  top_n_index,
1349
1332
  top_n_color,
1350
1333
  top_n_facet,
@@ -1355,107 +1338,92 @@ def plot_facet_stacked_bars(
1355
1338
  sort_values_facet=sort_values_facet,
1356
1339
  )
1357
1340
 
1358
- facets = sorted(
1359
- aggregated_df["facet"].unique()
1360
- ) # Ensure facets are sorted consistently
1341
+ aggregated_df['index'] = aggregated_df['index'].astype(str)
1342
+ aggregated_df['col'] = aggregated_df['col'].astype(str)
1343
+ aggregated_df['facet'] = aggregated_df['facet'].astype(str)
1361
1344
 
1362
- columns = sorted(
1363
- aggregated_df.groupby("col", observed=True)["value"]
1364
- .sum()
1365
- .sort_values(ascending=False)
1366
- .index.tolist()
1367
- )
1368
- column_colors = assign_column_colors(columns, color_palette, null_label)
1369
-
1370
- fig = make_subplots(
1371
- rows=-(-len(facets) // subplots_per_row),
1372
- cols=min(subplots_per_row, len(facets)),
1373
- subplot_titles=facets,
1374
- )
1345
+ # --- Store original 'value' for annotations before potential scaling ---
1346
+ aggregated_df['annotation_value'] = aggregated_df['value'].copy()
1347
+ # ----------------------------------------------------------------------
1375
1348
 
1376
- # * relative?
1377
1349
  if relative:
1350
+ # This transforms the bar heights (value column) to percentages (0-1 range)
1378
1351
  aggregated_df["value"] = aggregated_df.groupby(["facet", "index"])["value"].transform(lambda x: x / x.sum())
1379
- fig.update_layout(yaxis_tickformat=".0%") # Show as percentage
1380
-
1381
- # * Ensure all categories appear in the legend by adding an invisible trace
1382
- for column in columns:
1383
- fig.add_trace(
1384
- go.Bar(
1385
- x=[None], # Invisible bar
1386
- y=[None],
1387
- name=column,
1388
- marker=dict(color=column_colors[column]),
1389
- showlegend=True, # Ensure it appears in the legend
1390
- )
1391
- )
1392
1352
 
1393
- added_to_legend = set()
1394
- for i, facet in enumerate(facets):
1395
- facet_data = aggregated_df[aggregated_df["facet"] == facet]
1396
- row = (i // subplots_per_row) + 1
1397
- col = (i % subplots_per_row) + 1
1398
-
1399
- for column in columns:
1400
- column_data = facet_data[facet_data["col"] == column]
1401
-
1402
- show_legend = column not in added_to_legend
1403
- if show_legend:
1404
- added_to_legend.add(column)
1405
-
1406
- fig.add_trace(
1407
- go.Bar(
1408
- x=column_data["index"],
1409
- y=column_data["value"],
1410
- name=column,
1411
- marker=dict(color=column_colors[column]),
1412
- legendgroup=column, # Ensures multiple traces use the same legend entry
1413
- showlegend=False, # suppress further legend items
1414
- ),
1415
- row=row,
1416
- col=col,
1417
- )
1353
+ category_orders = {}
1418
1354
 
1419
- if annotations:
1420
- for _, row_data in column_data.iterrows():
1421
- fig.add_annotation(
1422
- x=row_data["index"],
1423
- y=row_data["value"],
1424
- text=f"{row_data['value']:.{precision}f}",
1425
- showarrow=False,
1426
- row=row,
1427
- col=col,
1428
- )
1429
-
1430
- unique_rows = len(aggregated_df)
1431
- axis_details = []
1432
- if top_n_index > 0:
1433
- axis_details.append(f"TOP {top_n_index} [{original_column_names[0]}]")
1434
- else:
1435
- axis_details.append(f"[{original_column_names[0]}]")
1355
+ if sort_values_index:
1356
+ sum_by_index = aggregated_df.groupby('index')['value'].sum().sort_values(ascending=False)
1357
+ category_orders["index"] = sum_by_index.index.tolist()
1436
1358
 
1437
- if top_n_color > 0:
1438
- axis_details.append(f"TOP {top_n_color} [{original_column_names[1]}]")
1439
- else:
1440
- axis_details.append(f"[{original_column_names[1]}]")
1359
+ if sort_values_color:
1360
+ sum_by_col = aggregated_df.groupby('col')['value'].sum().sort_values(ascending=False)
1361
+ category_orders["col"] = sum_by_col.index.tolist()
1362
+
1363
+ if sort_values_facet:
1364
+ sum_by_facet = aggregated_df.groupby('facet')['value'].sum().sort_values(ascending=False)
1365
+ category_orders["facet"] = sum_by_facet.index.tolist()
1441
1366
 
1442
- if top_n_facet > 0:
1443
- axis_details.append(f"TOP {top_n_facet} [{original_column_names[2]}]")
1367
+ columns_for_color = sorted(aggregated_df["col"].unique().tolist())
1368
+ column_colors_map = assign_column_colors(columns_for_color, color_palette, null_label) # Assumes assign_column_colors is accessible
1369
+
1370
+ # --- Prepare the text series for annotations with 'show_pct' control ---
1371
+ if annotations:
1372
+ if show_pct:
1373
+ # When show_pct is True, use the scaled 'value' column (0-1) and format as percentage
1374
+ formatted_text_series = aggregated_df["value"].apply(lambda x: f"{x:.{precision}%}".replace('.', ','))
1375
+ else:
1376
+ # When show_pct is False, use the 'annotation_value' (original absolute) and format as absolute
1377
+ formatted_text_series = aggregated_df["annotation_value"].apply(lambda x: f"{x:_.{precision}f}".replace('.', ','))
1444
1378
  else:
1445
- axis_details.append(f"[{original_column_names[2]}]")
1379
+ formatted_text_series = None
1380
+ # -----------------------------------------------------------------------
1446
1381
 
1447
- title = f"{caption} {', '.join(axis_details)}, n = {original_rows:_} ({n:_})"
1448
- template = "plotly_dark" if os.getenv("THEME") == "dark" else "plotly"
1449
-
1450
- fig.update_layout(
1451
- title=title,
1382
+ fig = px.bar(
1383
+ aggregated_df,
1384
+ x="index",
1385
+ y="value",
1386
+ color="col",
1387
+ facet_col="facet",
1388
+ facet_col_wrap=subplots_per_row,
1452
1389
  barmode="stack",
1453
- height=subplot_size * (-(-len(facets) // subplots_per_row)),
1454
- width=subplot_size * min(subplots_per_row, len(facets)),
1455
- showlegend=True,
1456
- template=template,
1390
+ color_discrete_map=column_colors_map,
1391
+ category_orders=category_orders,
1392
+ text=formatted_text_series,
1393
+ text_auto=False,
1394
+ height=subplot_size * (-(-len(aggregated_df["facet"].unique()) // subplots_per_row)),
1395
+ title=f"{caption} {original_column_names[0]}, {original_column_names[1]}, {original_column_names[2]}",
1457
1396
  )
1458
1397
 
1398
+ fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
1399
+
1400
+ fig.update_xaxes(matches=None)
1401
+ for axis in fig.layout:
1402
+ if axis.startswith("xaxis"):
1403
+ fig.layout[axis].showticklabels = True
1404
+
1405
+ template = "plotly_dark" if os.getenv("THEME") == "dark" else "plotly"
1406
+
1407
+ layout_updates = {
1408
+ "title_text": f"{caption} "
1409
+ f"{'TOP ' + str(top_n_index) + ' ' if top_n_index > 0 else ''}[{original_column_names[0]}] "
1410
+ f"{'TOP ' + str(top_n_color) + ' ' if top_n_color > 0 else ''}[{original_column_names[1]}] "
1411
+ f"{'TOP ' + str(top_n_facet) + ' ' if top_n_facet > 0 else ''}[{original_column_names[2]}] "
1412
+ f", n = {original_rows:_} ({n:_})",
1413
+ "showlegend": True,
1414
+ "template": template,
1415
+ "width": subplot_size * subplots_per_row,
1416
+ }
1417
+
1418
+ if relative:
1419
+ layout_updates['yaxis_range'] = [0, 1.1]
1420
+ layout_updates['yaxis_tickformat'] = ".0%"
1421
+
1422
+ fig.update_layout(**layout_updates)
1423
+
1424
+ if relative:
1425
+ fig.update_yaxes(tickformat=".0%")
1426
+
1459
1427
  if png_path:
1460
1428
  png_path = Path(png_path)
1461
1429
  fig.write_image(str(png_path))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pandas-plots
3
- Version: 0.12.22
3
+ Version: 0.12.24
4
4
  Summary: A collection of helper for table handling and visualization
5
5
  Home-page: https://github.com/smeisegeier/pandas-plots
6
6
  Author: smeisegeier
@@ -0,0 +1,11 @@
1
+ pandas_plots/hlp.py,sha256=kSqoGMEaOtC94wtTS7CMFXMgptv-2tSOMf5Zm7euhpI,20838
2
+ pandas_plots/pii.py,sha256=2WKE-W9s285jPdsTqCgt1uxuW4lj1PYCVOYB2fYDNwQ,2195
3
+ pandas_plots/pls.py,sha256=jFsHvjG8fvLBdHpaYOX_5TgpDrcA5bMWjAUtXb6bVXo,48629
4
+ pandas_plots/tbl.py,sha256=RJWBHeKGTAhGpVCY57TsS_dYR-FpInP-TOsKW_tU4V4,32556
5
+ pandas_plots/ven.py,sha256=2x3ACo2vSfO3q6fv-UdDQ0h1SJyt8WChBGgE5SDCdCk,11673
6
+ pandas_plots-0.12.24.dist-info/licenses/LICENSE,sha256=ltLbQWUCs-GBQlTPXbt5nHNBE9U5LzjjoS1Y8hHETM4,1051
7
+ pandas_plots-0.12.24.dist-info/METADATA,sha256=5519ufLPkBZEaylDrN6lC-D5Rtc7xr4tGQVNDtW_5Ms,7564
8
+ pandas_plots-0.12.24.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
9
+ pandas_plots-0.12.24.dist-info/pii.py,sha256=2WKE-W9s285jPdsTqCgt1uxuW4lj1PYCVOYB2fYDNwQ,2195
10
+ pandas_plots-0.12.24.dist-info/top_level.txt,sha256=XnaNuIHBqMmCeh_U7nKOYTwFue_SIA0wxuDgdPmnnSk,13
11
+ pandas_plots-0.12.24.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,4 +1,4 @@
1
- Copyright 2024 smeisegeier
1
+ Copyright 2025 smeisegeier
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
@@ -1,11 +0,0 @@
1
- pandas_plots/hlp.py,sha256=i11Ep9P-u9O0bvexGTELRDUtmLzvNgNHxnkQTGf3DwQ,20838
2
- pandas_plots/pii.py,sha256=2WKE-W9s285jPdsTqCgt1uxuW4lj1PYCVOYB2fYDNwQ,2195
3
- pandas_plots/pls.py,sha256=APvF_cEYN28TtlpNNIJ2NPTA3chTP9ZHtwnVEuZ-skI,49059
4
- pandas_plots/tbl.py,sha256=RJWBHeKGTAhGpVCY57TsS_dYR-FpInP-TOsKW_tU4V4,32556
5
- pandas_plots/ven.py,sha256=2x3ACo2vSfO3q6fv-UdDQ0h1SJyt8WChBGgE5SDCdCk,11673
6
- pandas_plots-0.12.22.dist-info/licenses/LICENSE,sha256=6KQ5KVAAhRaB-JJKpX4cefKvRZRgI7GUPc92_2d31XY,1051
7
- pandas_plots-0.12.22.dist-info/METADATA,sha256=0bdvEP5M1SgmSJI3QKLd8MX1RjSrwzxlXWrygQNjHaM,7564
8
- pandas_plots-0.12.22.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
9
- pandas_plots-0.12.22.dist-info/pii.py,sha256=2WKE-W9s285jPdsTqCgt1uxuW4lj1PYCVOYB2fYDNwQ,2195
10
- pandas_plots-0.12.22.dist-info/top_level.txt,sha256=XnaNuIHBqMmCeh_U7nKOYTwFue_SIA0wxuDgdPmnnSk,13
11
- pandas_plots-0.12.22.dist-info/RECORD,,