apmtools 0.2.26__tar.gz → 0.2.28__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.
- {apmtools-0.2.26 → apmtools-0.2.28}/PKG-INFO +1 -1
- {apmtools-0.2.26 → apmtools-0.2.28}/pyproject.toml +1 -1
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/shiny/summary.py +319 -319
- {apmtools-0.2.26 → apmtools-0.2.28}/.gitignore +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/LICENSE +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/README.md +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/__init__.py +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/classes.py +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/data_processing.py +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/functions.py +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/legacy.py +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/shiny/__init__.py +0 -0
- {apmtools-0.2.26 → apmtools-0.2.28}/src/apmtools/units.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: apmtools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.028
|
|
4
4
|
Summary: A collection of tools for processing air pollution monitoring data
|
|
5
5
|
Project-URL: Homepage, https://github.com/federlorenz/apmtools
|
|
6
6
|
Author-email: Federico Lorenzetti <lorenzetti.federico@gmail.com>
|
|
@@ -508,279 +508,307 @@ def create_summary_app(
|
|
|
508
508
|
|
|
509
509
|
@module.server
|
|
510
510
|
def plot_tab_server(input, output, session, initial_filters=None):
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
choices={c: c for c in choices},
|
|
576
|
-
selected=selected, multiple=True,
|
|
577
|
-
)
|
|
578
|
-
return ui.input_select(
|
|
579
|
-
"x", "X-axis / variable",
|
|
511
|
+
initial_filters = initial_filters or {}
|
|
512
|
+
|
|
513
|
+
@render.ui
|
|
514
|
+
def subset_filters():
|
|
515
|
+
controls = []
|
|
516
|
+
for i, name in enumerate(list(input.subset_columns())):
|
|
517
|
+
if name not in column_map:
|
|
518
|
+
continue
|
|
519
|
+
choices, _ = _subset_choices(summary, column_map[name])
|
|
520
|
+
controls.append(ui.input_selectize(
|
|
521
|
+
f"subset_values_{i}", f"{name} values", choices=choices,
|
|
522
|
+
selected=initial_filters.get(
|
|
523
|
+
"values", {}).get(name, []),
|
|
524
|
+
multiple=True,
|
|
525
|
+
))
|
|
526
|
+
return ui.div(*controls) if controls else ui.help_text("Select columns to filter this plot.")
|
|
527
|
+
|
|
528
|
+
@reactive.calc
|
|
529
|
+
def filter_state():
|
|
530
|
+
names = list(input.subset_columns())
|
|
531
|
+
values = {}
|
|
532
|
+
for i, name in enumerate(names):
|
|
533
|
+
if name in column_map:
|
|
534
|
+
values[name] = list(input[f"subset_values_{i}"]())
|
|
535
|
+
return {"columns": names, "values": values,
|
|
536
|
+
"start": input.datetime_start().strip(),
|
|
537
|
+
"end": input.datetime_end().strip()}
|
|
538
|
+
|
|
539
|
+
@reactive.calc
|
|
540
|
+
def filtered_summary():
|
|
541
|
+
state = filter_state()
|
|
542
|
+
filters = {}
|
|
543
|
+
for name, keys in state["values"].items():
|
|
544
|
+
_, mapping = _subset_choices(summary, column_map[name])
|
|
545
|
+
selected = [mapping[key] for key in keys if key in mapping]
|
|
546
|
+
if selected:
|
|
547
|
+
filters[column_map[name]] = selected
|
|
548
|
+
return _apply_filters(summary, filters, state["start"], state["end"])
|
|
549
|
+
|
|
550
|
+
@render.data_frame
|
|
551
|
+
def plot_table():
|
|
552
|
+
df = selected_data()
|
|
553
|
+
return render.DataGrid(pd.DataFrame(df).reset_index(drop=True),
|
|
554
|
+
filters=True, height="420px")
|
|
555
|
+
|
|
556
|
+
@render.ui
|
|
557
|
+
def x_control():
|
|
558
|
+
kind = input.plot_type()
|
|
559
|
+
if kind in ("box", "bar"):
|
|
560
|
+
choices = categorical_columns
|
|
561
|
+
multiple = True
|
|
562
|
+
elif kind == "line":
|
|
563
|
+
choices = datetime_columns
|
|
564
|
+
multiple = False
|
|
565
|
+
else:
|
|
566
|
+
choices = numeric_columns
|
|
567
|
+
multiple = False
|
|
568
|
+
|
|
569
|
+
if not choices:
|
|
570
|
+
return ui.help_text(f"No compatible X variables for {kind}.")
|
|
571
|
+
selected = [x] if x in choices else [choices[0]]
|
|
572
|
+
if multiple:
|
|
573
|
+
return ui.input_selectize(
|
|
574
|
+
"x", "X-axis / categories",
|
|
580
575
|
choices={c: c for c in choices},
|
|
581
|
-
selected=selected
|
|
576
|
+
selected=selected, multiple=True,
|
|
582
577
|
)
|
|
578
|
+
return ui.input_select(
|
|
579
|
+
"x", "X-axis / variable",
|
|
580
|
+
choices={c: c for c in choices},
|
|
581
|
+
selected=selected[0],
|
|
582
|
+
)
|
|
583
583
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
choices={c: c for c in numeric_columns},
|
|
597
|
-
selected=selected,
|
|
598
|
-
)
|
|
599
|
-
return ui.input_selectize(
|
|
600
|
-
"y", "Y-axis / variables",
|
|
584
|
+
@render.ui
|
|
585
|
+
def y_control():
|
|
586
|
+
kind = input.plot_type()
|
|
587
|
+
if kind == "histogram":
|
|
588
|
+
return ui.div()
|
|
589
|
+
if not numeric_columns:
|
|
590
|
+
return ui.help_text("No numeric Y variables available.")
|
|
591
|
+
selected = next(
|
|
592
|
+
(v for v in initial_y if v in numeric_columns), numeric_columns[0])
|
|
593
|
+
if kind in ("box", "bar", "scatter"):
|
|
594
|
+
return ui.input_select(
|
|
595
|
+
"y", "Y-axis / variable",
|
|
601
596
|
choices={c: c for c in numeric_columns},
|
|
602
|
-
selected=
|
|
603
|
-
selected],
|
|
604
|
-
multiple=True,
|
|
597
|
+
selected=selected,
|
|
605
598
|
)
|
|
599
|
+
return ui.input_selectize(
|
|
600
|
+
"y", "Y-axis / variables",
|
|
601
|
+
choices={c: c for c in numeric_columns},
|
|
602
|
+
selected=[v for v in initial_y if v in numeric_columns] or [
|
|
603
|
+
selected],
|
|
604
|
+
multiple=True,
|
|
605
|
+
)
|
|
606
606
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
y_input, str) else list(y_input or []))
|
|
623
|
-
if not value_names:
|
|
624
|
-
return pd.DataFrame()
|
|
625
|
-
|
|
626
|
-
selected_groups = list(input.group_by())
|
|
627
|
-
grouping_by = [column_map[c] for c in selected_groups]
|
|
628
|
-
frequency = FREQUENCIES[input.frequency()]
|
|
629
|
-
aggregate = AGGREGATES[input.aggregate()]
|
|
630
|
-
value_columns = [column_map[c] for c in value_names]
|
|
631
|
-
|
|
632
|
-
if kind in ("box", "bar"):
|
|
633
|
-
for c in selected_x:
|
|
634
|
-
if column_map[c] not in grouping_by:
|
|
635
|
-
grouping_by.append(column_map[c])
|
|
636
|
-
if kind == "scatter" and frequency is not None:
|
|
637
|
-
# Both numerical axes must be aggregated on the same rows.
|
|
638
|
-
value_columns = list(dict.fromkeys(
|
|
639
|
-
[column_map[selected_x[0]], *value_columns]
|
|
640
|
-
))
|
|
641
|
-
if frequency is not None:
|
|
642
|
-
current = _aggregate(
|
|
643
|
-
current, value_columns, frequency, grouping_by, aggregate
|
|
644
|
-
)
|
|
645
|
-
return current
|
|
646
|
-
|
|
647
|
-
@render_plotly
|
|
648
|
-
def plot():
|
|
649
|
-
plot_df = selected_data()
|
|
650
|
-
selected_plot = input.plot_type()
|
|
651
|
-
x_input = input.x()
|
|
652
|
-
selected_x = ([x_input] if isinstance(
|
|
653
|
-
x_input, str) else list(x_input or []))
|
|
654
|
-
y_input = None if selected_plot == "histogram" else input.y()
|
|
655
|
-
selected_y = ([y_input] if isinstance(
|
|
607
|
+
@reactive.calc
|
|
608
|
+
def selected_data():
|
|
609
|
+
current = filtered_summary()
|
|
610
|
+
kind = input.plot_type()
|
|
611
|
+
x_input = input.x()
|
|
612
|
+
selected_x = ([x_input] if isinstance(
|
|
613
|
+
x_input, str) else list(x_input or []))
|
|
614
|
+
if not selected_x:
|
|
615
|
+
return pd.DataFrame()
|
|
616
|
+
|
|
617
|
+
if kind == "histogram":
|
|
618
|
+
value_names = selected_x
|
|
619
|
+
else:
|
|
620
|
+
y_input = input.y()
|
|
621
|
+
value_names = ([y_input] if isinstance(
|
|
656
622
|
y_input, str) else list(y_input or []))
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
623
|
+
if not value_names:
|
|
624
|
+
return pd.DataFrame()
|
|
625
|
+
|
|
626
|
+
selected_groups = list(input.group_by())
|
|
627
|
+
grouping_by = [column_map[c] for c in selected_groups]
|
|
628
|
+
frequency = FREQUENCIES[input.frequency()]
|
|
629
|
+
aggregate = AGGREGATES[input.aggregate()]
|
|
630
|
+
value_columns = [column_map[c] for c in value_names]
|
|
631
|
+
|
|
632
|
+
if kind in ("box", "bar"):
|
|
633
|
+
for c in selected_x:
|
|
634
|
+
if column_map[c] not in grouping_by:
|
|
635
|
+
grouping_by.append(column_map[c])
|
|
636
|
+
if kind == "scatter" and frequency is not None:
|
|
637
|
+
# Both numerical axes must be aggregated on the same rows.
|
|
638
|
+
value_columns = list(dict.fromkeys(
|
|
639
|
+
[column_map[selected_x[0]], *value_columns]
|
|
640
|
+
))
|
|
641
|
+
if frequency is not None:
|
|
642
|
+
current = _aggregate(
|
|
643
|
+
current, value_columns, frequency, grouping_by, aggregate
|
|
644
|
+
)
|
|
645
|
+
return current
|
|
646
|
+
|
|
647
|
+
@render_plotly
|
|
648
|
+
def plot():
|
|
649
|
+
plot_df = selected_data()
|
|
650
|
+
selected_plot = input.plot_type()
|
|
651
|
+
x_input = input.x()
|
|
652
|
+
selected_x = ([x_input] if isinstance(
|
|
653
|
+
x_input, str) else list(x_input or []))
|
|
654
|
+
y_input = None if selected_plot == "histogram" else input.y()
|
|
655
|
+
selected_y = ([y_input] if isinstance(
|
|
656
|
+
y_input, str) else list(y_input or []))
|
|
657
|
+
selected_groups = list(input.group_by())
|
|
658
|
+
frequency = FREQUENCIES[input.frequency()]
|
|
659
|
+
|
|
660
|
+
if plot_df.empty or not selected_x or (
|
|
661
|
+
selected_plot != "histogram" and not selected_y
|
|
662
|
+
):
|
|
663
|
+
return px.scatter(title="No data for the selected filters")
|
|
664
|
+
|
|
665
|
+
aggregate = AGGREGATES[input.aggregate()]
|
|
666
|
+
|
|
667
|
+
def mapped(column):
|
|
668
|
+
original = column_map[column]
|
|
669
|
+
return f"{original}_{aggregate}" if frequency is not None else original
|
|
670
|
+
|
|
671
|
+
if selected_plot == "histogram":
|
|
672
|
+
x_column = mapped(selected_x[0])
|
|
673
|
+
y_columns = []
|
|
674
|
+
elif selected_plot in ("box", "bar"):
|
|
675
|
+
x_columns = [column_map[c] for c in selected_x]
|
|
676
|
+
x_column = x_columns[0]
|
|
677
|
+
y_columns = [mapped(selected_y[0])]
|
|
678
|
+
elif selected_plot == "scatter":
|
|
679
|
+
x_column = mapped(selected_x[0])
|
|
680
|
+
y_columns = [mapped(selected_y[0])]
|
|
681
|
+
else:
|
|
682
|
+
x_column = frequency if frequency is not None else column_map[selected_x[0]]
|
|
683
|
+
y_columns = [mapped(c) for c in selected_y]
|
|
684
|
+
|
|
685
|
+
required = [x_column, *y_columns]
|
|
686
|
+
if selected_plot in ("box", "bar"):
|
|
687
|
+
required.extend(x_columns)
|
|
688
|
+
if any(c not in plot_df.columns for c in required):
|
|
689
|
+
return px.scatter(title="Selected variables are unavailable after aggregation")
|
|
690
|
+
|
|
691
|
+
if selected_plot in ("box", "bar"):
|
|
692
|
+
plot_df = plot_df.copy()
|
|
693
|
+
plot_df["__plot_category"] = plot_df[x_columns].astype(
|
|
694
|
+
str).agg(" | ".join, axis=1)
|
|
695
|
+
x_column = "__plot_category"
|
|
696
|
+
|
|
697
|
+
# Multiple grouping columns are supported by group_stat(). For
|
|
698
|
+
# plotting, Plotly needs one color/category column, so combine the
|
|
699
|
+
# selected grouping columns into a readable composite label.
|
|
700
|
+
group_columns = [column_map[c] for c in selected_groups
|
|
701
|
+
if column_map[c] in plot_df.columns]
|
|
702
|
+
if group_columns:
|
|
703
|
+
plot_df = plot_df.copy()
|
|
704
|
+
plot_df["__plot_group"] = (
|
|
705
|
+
plot_df[group_columns].astype(
|
|
694
706
|
str).agg(" | ".join, axis=1)
|
|
695
|
-
|
|
707
|
+
)
|
|
696
708
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
709
|
+
if selected_plot == "histogram":
|
|
710
|
+
fig = _histogram_figure(
|
|
711
|
+
plot_df,
|
|
712
|
+
x_column,
|
|
713
|
+
group_column="__plot_group" if group_columns else None,
|
|
714
|
+
bins=int(input.bins()),
|
|
715
|
+
density=input.hist_density() == "True",
|
|
716
|
+
stacked=input.hist_stacked() == "True",
|
|
717
|
+
histtype=input.hist_type(),
|
|
718
|
+
cumulative=int(input.hist_cumulative()),
|
|
719
|
+
)
|
|
720
|
+
elif selected_plot == "box":
|
|
721
|
+
fig = px.box(
|
|
722
|
+
plot_df, x=x_column, y=y_columns[0],
|
|
723
|
+
color="__plot_group" if group_columns else None,
|
|
724
|
+
points="outliers",
|
|
725
|
+
)
|
|
726
|
+
elif selected_plot == "bar":
|
|
727
|
+
fig = px.bar(
|
|
728
|
+
plot_df, x=x_column, y=y_columns[0],
|
|
729
|
+
color="__plot_group" if group_columns else None,
|
|
730
|
+
barmode="group",
|
|
731
|
+
)
|
|
732
|
+
elif selected_plot == "scatter":
|
|
733
|
+
fig = px.scatter(
|
|
734
|
+
plot_df, x=x_column, y=y_columns[0],
|
|
735
|
+
color="__plot_group" if group_columns else None,
|
|
736
|
+
)
|
|
737
|
+
else:
|
|
738
|
+
# ``identifier`` identifies an individual input monitor. It
|
|
739
|
+
# must therefore be used as Plotly's line-group key even when
|
|
740
|
+
# it is not one of the user-selected colour/group columns.
|
|
741
|
+
# This prevents observations from two different monitors
|
|
742
|
+
# being connected by a line merely because they share the
|
|
743
|
+
# same selected group. The identifier is deliberately NOT
|
|
744
|
+
# used for colour, so monitors in the same selected group
|
|
745
|
+
# retain the same colour. After frequency aggregation,
|
|
746
|
+
# identifier is only available if it was selected as a
|
|
747
|
+
# grouping variable. If it is absent, discontinuous runs are
|
|
748
|
+
# inferred from gaps larger than the selected frequency.
|
|
749
|
+
identifier_column = (
|
|
750
|
+
column_map["identifier"]
|
|
751
|
+
if (
|
|
752
|
+
"identifier" in column_map
|
|
753
|
+
and column_map["identifier"] in plot_df.columns
|
|
707
754
|
)
|
|
755
|
+
else None
|
|
756
|
+
)
|
|
708
757
|
|
|
709
|
-
|
|
710
|
-
|
|
758
|
+
section_column = None
|
|
759
|
+
if frequency is not None and identifier_column is None:
|
|
760
|
+
plot_df = _add_gap_sections(
|
|
711
761
|
plot_df,
|
|
712
|
-
x_column,
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
density=input.hist_density() == "True",
|
|
716
|
-
stacked=input.hist_stacked() == "True",
|
|
717
|
-
histtype=input.hist_type(),
|
|
718
|
-
cumulative=int(input.hist_cumulative()),
|
|
719
|
-
)
|
|
720
|
-
elif selected_plot == "box":
|
|
721
|
-
fig = px.box(
|
|
722
|
-
plot_df, x=x_column, y=y_columns[0],
|
|
723
|
-
color="__plot_group" if group_columns else None,
|
|
724
|
-
points="outliers",
|
|
725
|
-
)
|
|
726
|
-
elif selected_plot == "bar":
|
|
727
|
-
fig = px.bar(
|
|
728
|
-
plot_df, x=x_column, y=y_columns[0],
|
|
729
|
-
color="__plot_group" if group_columns else None,
|
|
730
|
-
barmode="group",
|
|
762
|
+
x_column=x_column,
|
|
763
|
+
group_columns=group_columns,
|
|
764
|
+
frequency=frequency,
|
|
731
765
|
)
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
766
|
+
section_column = "__line_section"
|
|
767
|
+
|
|
768
|
+
if group_columns:
|
|
769
|
+
group_column = "__plot_group"
|
|
770
|
+
line_columns = [x_column, group_column, *y_columns]
|
|
771
|
+
line_group_column = identifier_column or section_column
|
|
772
|
+
if line_group_column is not None:
|
|
773
|
+
line_columns.append(line_group_column)
|
|
774
|
+
|
|
775
|
+
# Keep whichever column controls line segmentation as an
|
|
776
|
+
# id_var during melt(). Otherwise pandas drops it and
|
|
777
|
+
# Plotly cannot resolve line_group (e.g. __line_section).
|
|
778
|
+
id_vars = [x_column, group_column]
|
|
779
|
+
if line_group_column is not None:
|
|
780
|
+
id_vars.append(line_group_column)
|
|
781
|
+
|
|
782
|
+
long_df = plot_df[line_columns].melt(
|
|
783
|
+
id_vars=id_vars,
|
|
784
|
+
var_name="variable",
|
|
785
|
+
value_name="value",
|
|
736
786
|
)
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
# retain the same colour. After frequency aggregation,
|
|
746
|
-
# identifier is only available if it was selected as a
|
|
747
|
-
# grouping variable. If it is absent, discontinuous runs are
|
|
748
|
-
# inferred from gaps larger than the selected frequency.
|
|
749
|
-
identifier_column = (
|
|
750
|
-
column_map["identifier"]
|
|
751
|
-
if (
|
|
752
|
-
"identifier" in column_map
|
|
753
|
-
and column_map["identifier"] in plot_df.columns
|
|
754
|
-
)
|
|
755
|
-
else None
|
|
787
|
+
fig = px.line(
|
|
788
|
+
long_df,
|
|
789
|
+
x=x_column,
|
|
790
|
+
y="value",
|
|
791
|
+
color=group_column,
|
|
792
|
+
line_group=line_group_column,
|
|
793
|
+
facet_row="variable" if len(
|
|
794
|
+
y_columns) > 1 else None,
|
|
756
795
|
)
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
796
|
+
elif identifier_column is not None:
|
|
797
|
+
# With no explicit grouping, retain the normal Y-variable
|
|
798
|
+
# colouring while using identifier to keep each monitor's
|
|
799
|
+
# time series separate.
|
|
800
|
+
if len(y_columns) == 1:
|
|
801
|
+
fig = px.line(
|
|
761
802
|
plot_df,
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
803
|
+
x=x_column,
|
|
804
|
+
y=y_columns[0],
|
|
805
|
+
line_group=identifier_column,
|
|
765
806
|
)
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
group_column = "__plot_group"
|
|
770
|
-
line_columns = [x_column, group_column, *y_columns]
|
|
771
|
-
line_group_column = identifier_column or section_column
|
|
772
|
-
if line_group_column is not None:
|
|
773
|
-
line_columns.append(line_group_column)
|
|
774
|
-
|
|
775
|
-
# Keep whichever column controls line segmentation as an
|
|
776
|
-
# id_var during melt(). Otherwise pandas drops it and
|
|
777
|
-
# Plotly cannot resolve line_group (e.g. __line_section).
|
|
778
|
-
id_vars = [x_column, group_column]
|
|
779
|
-
if line_group_column is not None:
|
|
780
|
-
id_vars.append(line_group_column)
|
|
781
|
-
|
|
807
|
+
else:
|
|
808
|
+
line_columns = [
|
|
809
|
+
x_column, identifier_column, *y_columns]
|
|
782
810
|
long_df = plot_df[line_columns].melt(
|
|
783
|
-
id_vars=
|
|
811
|
+
id_vars=[x_column, identifier_column],
|
|
784
812
|
var_name="variable",
|
|
785
813
|
value_name="value",
|
|
786
814
|
)
|
|
@@ -788,74 +816,46 @@ def create_summary_app(
|
|
|
788
816
|
long_df,
|
|
789
817
|
x=x_column,
|
|
790
818
|
y="value",
|
|
791
|
-
color=
|
|
792
|
-
line_group=
|
|
793
|
-
facet_row="variable"
|
|
794
|
-
|
|
819
|
+
color="variable",
|
|
820
|
+
line_group=identifier_column,
|
|
821
|
+
facet_row="variable",
|
|
822
|
+
)
|
|
823
|
+
elif section_column is not None:
|
|
824
|
+
if len(y_columns) == 1:
|
|
825
|
+
fig = px.line(
|
|
826
|
+
plot_df,
|
|
827
|
+
x=x_column,
|
|
828
|
+
y=y_columns[0],
|
|
829
|
+
line_group=section_column,
|
|
795
830
|
)
|
|
796
|
-
elif identifier_column is not None:
|
|
797
|
-
# With no explicit grouping, retain the normal Y-variable
|
|
798
|
-
# colouring while using identifier to keep each monitor's
|
|
799
|
-
# time series separate.
|
|
800
|
-
if len(y_columns) == 1:
|
|
801
|
-
fig = px.line(
|
|
802
|
-
plot_df,
|
|
803
|
-
x=x_column,
|
|
804
|
-
y=y_columns[0],
|
|
805
|
-
line_group=identifier_column,
|
|
806
|
-
)
|
|
807
|
-
else:
|
|
808
|
-
line_columns = [
|
|
809
|
-
x_column, identifier_column, *y_columns]
|
|
810
|
-
long_df = plot_df[line_columns].melt(
|
|
811
|
-
id_vars=[x_column, identifier_column],
|
|
812
|
-
var_name="variable",
|
|
813
|
-
value_name="value",
|
|
814
|
-
)
|
|
815
|
-
fig = px.line(
|
|
816
|
-
long_df,
|
|
817
|
-
x=x_column,
|
|
818
|
-
y="value",
|
|
819
|
-
color="variable",
|
|
820
|
-
line_group=identifier_column,
|
|
821
|
-
facet_row="variable",
|
|
822
|
-
)
|
|
823
|
-
elif section_column is not None:
|
|
824
|
-
if len(y_columns) == 1:
|
|
825
|
-
fig = px.line(
|
|
826
|
-
plot_df,
|
|
827
|
-
x=x_column,
|
|
828
|
-
y=y_columns[0],
|
|
829
|
-
line_group=section_column,
|
|
830
|
-
)
|
|
831
|
-
else:
|
|
832
|
-
line_columns = [
|
|
833
|
-
x_column, section_column, *y_columns]
|
|
834
|
-
long_df = plot_df[line_columns].melt(
|
|
835
|
-
id_vars=[x_column, section_column],
|
|
836
|
-
var_name="variable",
|
|
837
|
-
value_name="value",
|
|
838
|
-
)
|
|
839
|
-
fig = px.line(
|
|
840
|
-
long_df,
|
|
841
|
-
x=x_column,
|
|
842
|
-
y="value",
|
|
843
|
-
color="variable",
|
|
844
|
-
line_group=section_column,
|
|
845
|
-
facet_row="variable",
|
|
846
|
-
)
|
|
847
831
|
else:
|
|
848
|
-
|
|
832
|
+
line_columns = [
|
|
833
|
+
x_column, section_column, *y_columns]
|
|
834
|
+
long_df = plot_df[line_columns].melt(
|
|
835
|
+
id_vars=[x_column, section_column],
|
|
836
|
+
var_name="variable",
|
|
837
|
+
value_name="value",
|
|
838
|
+
)
|
|
839
|
+
fig = px.line(
|
|
840
|
+
long_df,
|
|
841
|
+
x=x_column,
|
|
842
|
+
y="value",
|
|
843
|
+
color="variable",
|
|
844
|
+
line_group=section_column,
|
|
845
|
+
facet_row="variable",
|
|
846
|
+
)
|
|
847
|
+
else:
|
|
848
|
+
fig = px.line(plot_df, x=x_column, y=y_columns)
|
|
849
849
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
850
|
+
fig.update_layout(
|
|
851
|
+
title=input.title(),
|
|
852
|
+
hovermode="x unified" if selected_plot == "line" else "closest",
|
|
853
|
+
margin=dict(l=60, r=30, t=70, b=60),
|
|
854
|
+
autosize=True,
|
|
855
|
+
)
|
|
856
|
+
return fig
|
|
857
857
|
|
|
858
|
-
|
|
858
|
+
return filter_state
|
|
859
859
|
|
|
860
860
|
app_ui = ui.page_fluid(
|
|
861
861
|
ui.tags.style("""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|