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 +1 -1
- pandas_plots/pls.py +104 -136
- {pandas_plots-0.12.22.dist-info → pandas_plots-0.12.24.dist-info}/METADATA +1 -1
- pandas_plots-0.12.24.dist-info/RECORD +11 -0
- {pandas_plots-0.12.22.dist-info → pandas_plots-0.12.24.dist-info}/WHEEL +1 -1
- {pandas_plots-0.12.22.dist-info → pandas_plots-0.12.24.dist-info}/licenses/LICENSE +1 -1
- pandas_plots-0.12.22.dist-info/RECORD +0 -11
- {pandas_plots-0.12.22.dist-info → pandas_plots-0.12.24.dist-info}/pii.py +0 -0
- {pandas_plots-0.12.22.dist-info → pandas_plots-0.12.24.dist-info}/top_level.txt +0 -0
pandas_plots/hlp.py
CHANGED
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]}]
|
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
|
-
) ->
|
1297
|
-
"""
|
1298
|
-
Create a grid of stacked bar charts.
|
1296
|
+
show_pct: bool = False,
|
1297
|
+
) -> go.Figure:
|
1299
1298
|
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
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
|
-
|
1326
|
-
|
1327
|
-
|
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
|
-
|
1313
|
+
df_copy = df.copy()
|
1331
1314
|
|
1332
|
-
if not (
|
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 =
|
1336
|
-
original_rows = len(df)
|
1318
|
+
original_column_names = df_copy.columns.tolist()
|
1337
1319
|
|
1338
|
-
if
|
1339
|
-
|
1340
|
-
|
1341
|
-
elif
|
1342
|
-
|
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
|
-
|
1347
|
-
|
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
|
-
|
1359
|
-
|
1360
|
-
|
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
|
-
|
1363
|
-
|
1364
|
-
|
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
|
-
|
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
|
-
|
1420
|
-
|
1421
|
-
|
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
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
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
|
-
|
1443
|
-
|
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
|
-
|
1379
|
+
formatted_text_series = None
|
1380
|
+
# -----------------------------------------------------------------------
|
1446
1381
|
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
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
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
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))
|
@@ -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,4 +1,4 @@
|
|
1
|
-
Copyright
|
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,,
|
File without changes
|
File without changes
|