eagleplot 3.0.0__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.
- eagleplot/Home.py +362 -0
- eagleplot/__init__.py +19 -0
- eagleplot/annotations.py +215 -0
- eagleplot/assets/logo.ico +0 -0
- eagleplot/assets/logo.png +0 -0
- eagleplot/assets/nccu-horiz-logo.png +0 -0
- eagleplot/assets/nccu-wings.png +0 -0
- eagleplot/batch.py +446 -0
- eagleplot/cli.py +719 -0
- eagleplot/data_tools.py +849 -0
- eagleplot/example-data/Artificial/bimodal_distribution.csv +501 -0
- eagleplot/example-data/Artificial/correlation_matrix.npy +0 -0
- eagleplot/example-data/Artificial/gaussian_surface.npy +0 -0
- eagleplot/example-data/Artificial/grouped_categorical.csv +241 -0
- eagleplot/example-data/Artificial/ir_spectrum.csv +1802 -0
- eagleplot/example-data/Artificial/molecular_properties.csv +121 -0
- eagleplot/example-data/Artificial/normal_distribution.csv +401 -0
- eagleplot/example-data/Artificial/reaction_kinetics.csv +121 -0
- eagleplot/example-data/Artificial/scatter_correlation.csv +201 -0
- eagleplot/example-data/Artificial/sine_cosine_waves.csv +301 -0
- eagleplot/gallery/box_plot.png +0 -0
- eagleplot/gallery/contour_plot.png +0 -0
- eagleplot/gallery/distribution_plot.png +0 -0
- eagleplot/gallery/heatmap.png +0 -0
- eagleplot/gallery/histogram.png +0 -0
- eagleplot/gallery/interactive_3d_surface.png +0 -0
- eagleplot/gallery/interactive_heatmap.png +0 -0
- eagleplot/gallery/interactive_histogram.png +0 -0
- eagleplot/gallery/interactive_line.png +0 -0
- eagleplot/gallery/interactive_scatter.png +0 -0
- eagleplot/gallery/line_plot.png +0 -0
- eagleplot/gallery/manifest.json +196 -0
- eagleplot/gallery/regression_plot.png +0 -0
- eagleplot/gallery/scatter_plot.png +0 -0
- eagleplot/io.py +1008 -0
- eagleplot/math.py +1210 -0
- eagleplot/molecular/__init__.py +21 -0
- eagleplot/molecular/atom_data.py +287 -0
- eagleplot/molecular/parsers.py +747 -0
- eagleplot/nist.py +241 -0
- eagleplot/pages/10_Molecular_Viz.py +225 -0
- eagleplot/pages/11_Batch_Analysis.py +325 -0
- eagleplot/pages/12_Help.py +199 -0
- eagleplot/pages/13_Settings.py +388 -0
- eagleplot/pages/1_Data_Upload.py +521 -0
- eagleplot/pages/2_Quick_Plot.py +1693 -0
- eagleplot/pages/3_Analysis_Tools.py +2510 -0
- eagleplot/pages/4_Multi_Plot_Dashboard.py +791 -0
- eagleplot/pages/5_Advanced_Plotting.py +691 -0
- eagleplot/pages/6_Export_Results.py +442 -0
- eagleplot/pages/7_Gallery.py +201 -0
- eagleplot/pages/8_Data_Tools.py +434 -0
- eagleplot/pages/9_Spectroscopy.py +821 -0
- eagleplot/pages/__init__.py +8 -0
- eagleplot/peaks.py +513 -0
- eagleplot/plotting.py +2630 -0
- eagleplot/plugin_loader.py +208 -0
- eagleplot/report.py +448 -0
- eagleplot/signal.py +494 -0
- eagleplot/spectroscopy.py +830 -0
- eagleplot/utils/__init__.py +76 -0
- eagleplot/utils/access.py +77 -0
- eagleplot/utils/data_preview.py +352 -0
- eagleplot/utils/plot_config.py +2721 -0
- eagleplot/utils/session_state.py +535 -0
- eagleplot/utils/theming.py +312 -0
- eagleplot/utils/user_settings.py +294 -0
- eagleplot-3.0.0.dist-info/METADATA +304 -0
- eagleplot-3.0.0.dist-info/RECORD +73 -0
- eagleplot-3.0.0.dist-info/WHEEL +5 -0
- eagleplot-3.0.0.dist-info/entry_points.txt +2 -0
- eagleplot-3.0.0.dist-info/licenses/LICENSE +21 -0
- eagleplot-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Utility modules for Streamlit GUI."""
|
|
2
|
+
|
|
3
|
+
from .session_state import (
|
|
4
|
+
initialize_session_state,
|
|
5
|
+
add_dataset,
|
|
6
|
+
get_current_dataset,
|
|
7
|
+
get_dataset,
|
|
8
|
+
delete_dataset,
|
|
9
|
+
add_plot_to_history,
|
|
10
|
+
clear_plot_history,
|
|
11
|
+
add_analysis_result,
|
|
12
|
+
save_session_to_file,
|
|
13
|
+
load_session_from_file,
|
|
14
|
+
clear_session,
|
|
15
|
+
get_session_summary,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .data_preview import (
|
|
19
|
+
preview_dataframe,
|
|
20
|
+
get_dataframe_info,
|
|
21
|
+
get_array_info,
|
|
22
|
+
format_data_size,
|
|
23
|
+
display_dataset_card,
|
|
24
|
+
get_column_suggestions,
|
|
25
|
+
display_data_preview,
|
|
26
|
+
get_plottable_arrays,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from .user_settings import (
|
|
30
|
+
get_config_path,
|
|
31
|
+
load_config,
|
|
32
|
+
save_config,
|
|
33
|
+
get_defaults,
|
|
34
|
+
save_defaults,
|
|
35
|
+
list_presets,
|
|
36
|
+
save_preset,
|
|
37
|
+
load_preset,
|
|
38
|
+
delete_preset,
|
|
39
|
+
SAVEABLE_KEYS,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Session state
|
|
44
|
+
"initialize_session_state",
|
|
45
|
+
"add_dataset",
|
|
46
|
+
"get_current_dataset",
|
|
47
|
+
"get_dataset",
|
|
48
|
+
"delete_dataset",
|
|
49
|
+
"add_plot_to_history",
|
|
50
|
+
"clear_plot_history",
|
|
51
|
+
"add_analysis_result",
|
|
52
|
+
"save_session_to_file",
|
|
53
|
+
"load_session_from_file",
|
|
54
|
+
"clear_session",
|
|
55
|
+
"get_session_summary",
|
|
56
|
+
# Data preview
|
|
57
|
+
"preview_dataframe",
|
|
58
|
+
"get_dataframe_info",
|
|
59
|
+
"get_array_info",
|
|
60
|
+
"format_data_size",
|
|
61
|
+
"display_dataset_card",
|
|
62
|
+
"get_column_suggestions",
|
|
63
|
+
"display_data_preview",
|
|
64
|
+
"get_plottable_arrays",
|
|
65
|
+
# User settings
|
|
66
|
+
"get_config_path",
|
|
67
|
+
"load_config",
|
|
68
|
+
"save_config",
|
|
69
|
+
"get_defaults",
|
|
70
|
+
"save_defaults",
|
|
71
|
+
"list_presets",
|
|
72
|
+
"save_preset",
|
|
73
|
+
"load_preset",
|
|
74
|
+
"delete_preset",
|
|
75
|
+
"SAVEABLE_KEYS",
|
|
76
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Gates for GUI features that reach outside the browser sandbox.
|
|
2
|
+
|
|
3
|
+
EaglePlot is meant to be run locally (``launch.bat`` / ``launch.command``), where
|
|
4
|
+
reading a folder on the user's own disk is the point. On a shared deployment the
|
|
5
|
+
same feature lets any visitor list and read files on the *server*. See audit A-15.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import warnings
|
|
12
|
+
from typing import Mapping, Optional
|
|
13
|
+
|
|
14
|
+
#: Environment variable that overrides the folder-loading default.
|
|
15
|
+
#: ``1``/``true``/``yes``/``on`` always allow; ``0``/``false``/``no``/``off`` always deny.
|
|
16
|
+
FOLDER_LOAD_ENV = "EAGLEPLOT_ALLOW_FOLDER_LOAD"
|
|
17
|
+
#: Name used before the Plottle -> EaglePlot rename (3.0.0). Still honoured, with a
|
|
18
|
+
#: deprecation warning, when the new name is not set. Remove in 4.0.
|
|
19
|
+
LEGACY_FOLDER_LOAD_ENV = "PLOTTLE_ALLOW_FOLDER_LOAD"
|
|
20
|
+
|
|
21
|
+
_TRUE = {"1", "true", "yes", "on"}
|
|
22
|
+
_FALSE = {"0", "false", "no", "off"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def folder_loading_allowed(env_value: Optional[str], client_ip: Optional[str]) -> bool:
|
|
26
|
+
"""Decide whether the GUI may read a folder path typed by the user.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
env_value : str or None
|
|
31
|
+
Value of :data:`FOLDER_LOAD_ENV`, or ``None`` if unset.
|
|
32
|
+
client_ip : str or None
|
|
33
|
+
``st.context.ip_address``. Streamlit reports ``None`` for a connection
|
|
34
|
+
from ``localhost`` and the remote address otherwise.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
bool
|
|
39
|
+
An explicit setting wins. Unset or unrecognised, folder loading is allowed
|
|
40
|
+
only for a local connection. The IP check alone is not a security control
|
|
41
|
+
(a reverse proxy on the same host looks local), so shared deployments
|
|
42
|
+
should set ``EAGLEPLOT_ALLOW_FOLDER_LOAD=0``.
|
|
43
|
+
"""
|
|
44
|
+
if env_value is not None:
|
|
45
|
+
value = env_value.strip().lower()
|
|
46
|
+
if value in _TRUE:
|
|
47
|
+
return True
|
|
48
|
+
if value in _FALSE:
|
|
49
|
+
return False
|
|
50
|
+
return client_ip is None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _client_ip() -> Optional[str]:
|
|
54
|
+
"""``st.context.ip_address`` for the current session (``None`` = localhost)."""
|
|
55
|
+
import streamlit as st
|
|
56
|
+
|
|
57
|
+
return st.context.ip_address
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def folder_loading_allowed_here(environ: Optional[Mapping[str, str]] = None) -> bool:
|
|
61
|
+
""":func:`folder_loading_allowed` for the current Streamlit session."""
|
|
62
|
+
env = os.environ if environ is None else environ
|
|
63
|
+
return folder_loading_allowed(folder_load_setting(env), _client_ip())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def folder_load_setting(env: Mapping[str, str]) -> Optional[str]:
|
|
67
|
+
"""The folder-loading setting from *env*, accepting the pre-3.0 variable name."""
|
|
68
|
+
value = env.get(FOLDER_LOAD_ENV)
|
|
69
|
+
if value is None and LEGACY_FOLDER_LOAD_ENV in env:
|
|
70
|
+
warnings.warn(
|
|
71
|
+
f"{LEGACY_FOLDER_LOAD_ENV} is deprecated; set {FOLDER_LOAD_ENV} instead "
|
|
72
|
+
"(Plottle was renamed EaglePlot in 3.0).",
|
|
73
|
+
DeprecationWarning,
|
|
74
|
+
stacklevel=2,
|
|
75
|
+
)
|
|
76
|
+
value = env[LEGACY_FOLDER_LOAD_ENV]
|
|
77
|
+
return value
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""Data Preview Utilities for Streamlit GUI.
|
|
2
|
+
|
|
3
|
+
This module provides helper functions for displaying data previews,
|
|
4
|
+
summaries, and metadata in the Streamlit interface.
|
|
5
|
+
|
|
6
|
+
Functions
|
|
7
|
+
---------
|
|
8
|
+
preview_dataframe(df, n_rows=10)
|
|
9
|
+
Display DataFrame preview with first and last rows
|
|
10
|
+
get_dataframe_info(df)
|
|
11
|
+
Get DataFrame information (shape, types, memory)
|
|
12
|
+
get_array_info(array)
|
|
13
|
+
Get NumPy array information (shape, dtype, stats)
|
|
14
|
+
format_data_size(size_bytes)
|
|
15
|
+
Format data size in human-readable format
|
|
16
|
+
display_dataset_card(name, data, metadata)
|
|
17
|
+
Display a dataset summary card in Streamlit
|
|
18
|
+
get_column_suggestions(df)
|
|
19
|
+
Suggest numeric/categorical columns for plotting
|
|
20
|
+
|
|
21
|
+
Examples
|
|
22
|
+
--------
|
|
23
|
+
>>> import streamlit as st
|
|
24
|
+
>>> import pandas as pd
|
|
25
|
+
>>> from eagleplot.utils.data_preview import preview_dataframe, get_dataframe_info
|
|
26
|
+
>>> df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})
|
|
27
|
+
>>> info = get_dataframe_info(df)
|
|
28
|
+
>>> st.write(info)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import streamlit as st
|
|
32
|
+
import numpy as np
|
|
33
|
+
import pandas as pd
|
|
34
|
+
from typing import Any, Dict, List, Tuple, Optional
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def preview_dataframe(df: pd.DataFrame, n_rows: int = 10) -> pd.DataFrame:
|
|
38
|
+
"""Display DataFrame preview with first and last rows.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
df : pd.DataFrame
|
|
43
|
+
DataFrame to preview
|
|
44
|
+
n_rows : int, default 10
|
|
45
|
+
Number of rows to show from head and tail
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
preview : pd.DataFrame
|
|
50
|
+
Preview DataFrame (first n_rows + last n_rows if applicable)
|
|
51
|
+
"""
|
|
52
|
+
if len(df) <= 2 * n_rows:
|
|
53
|
+
return df
|
|
54
|
+
else:
|
|
55
|
+
# Show first n_rows and last n_rows with separator
|
|
56
|
+
head = df.head(n_rows)
|
|
57
|
+
tail = df.tail(n_rows)
|
|
58
|
+
return pd.concat([head, tail])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@st.cache_data(show_spinner=False)
|
|
62
|
+
def get_dataframe_info(df: pd.DataFrame) -> Dict:
|
|
63
|
+
"""Get DataFrame information (shape, types, memory).
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
df : pd.DataFrame
|
|
68
|
+
DataFrame to analyze
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
info : dict
|
|
73
|
+
Dictionary with DataFrame metadata:
|
|
74
|
+
- shape: (rows, cols)
|
|
75
|
+
- columns: list of column names
|
|
76
|
+
- dtypes: dict of column types
|
|
77
|
+
- memory_usage: total memory in bytes
|
|
78
|
+
- missing_values: dict of missing counts per column
|
|
79
|
+
- numeric_columns: list of numeric column names
|
|
80
|
+
- categorical_columns: list of object/category column names
|
|
81
|
+
"""
|
|
82
|
+
info = {
|
|
83
|
+
"shape": df.shape,
|
|
84
|
+
"columns": list(df.columns),
|
|
85
|
+
"dtypes": {col: str(dtype) for col, dtype in df.dtypes.items()},
|
|
86
|
+
"memory_usage": df.memory_usage(deep=True).sum(),
|
|
87
|
+
"missing_values": df.isnull().sum().to_dict(),
|
|
88
|
+
"numeric_columns": list(df.select_dtypes(include=[np.number]).columns),
|
|
89
|
+
"categorical_columns": list(df.select_dtypes(include=["object", "category"]).columns),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return info
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@st.cache_data(show_spinner=False)
|
|
96
|
+
def get_array_info(array: np.ndarray) -> Dict:
|
|
97
|
+
"""Get NumPy array information (shape, dtype, stats).
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
array : np.ndarray
|
|
102
|
+
Array to analyze
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
info : dict
|
|
107
|
+
Dictionary with array metadata:
|
|
108
|
+
- shape: array shape
|
|
109
|
+
- dtype: data type
|
|
110
|
+
- size: total number of elements
|
|
111
|
+
- memory_usage: total memory in bytes
|
|
112
|
+
- ndim: number of dimensions
|
|
113
|
+
- min: minimum value (if numeric)
|
|
114
|
+
- max: maximum value (if numeric)
|
|
115
|
+
- mean: mean value (if numeric)
|
|
116
|
+
- std: standard deviation (if numeric)
|
|
117
|
+
"""
|
|
118
|
+
info = {
|
|
119
|
+
"shape": array.shape,
|
|
120
|
+
"dtype": str(array.dtype),
|
|
121
|
+
"size": array.size,
|
|
122
|
+
"memory_usage": array.nbytes,
|
|
123
|
+
"ndim": array.ndim,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Add statistics for numeric arrays
|
|
127
|
+
if np.issubdtype(array.dtype, np.number):
|
|
128
|
+
try:
|
|
129
|
+
info["min"] = float(np.min(array))
|
|
130
|
+
info["max"] = float(np.max(array))
|
|
131
|
+
info["mean"] = float(np.mean(array))
|
|
132
|
+
info["std"] = float(np.std(array))
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
return info
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def format_data_size(size_bytes: int) -> str:
|
|
140
|
+
"""Format data size in human-readable format.
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
size_bytes : int
|
|
145
|
+
Size in bytes
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
formatted : str
|
|
150
|
+
Human-readable size (e.g., "1.5 MB")
|
|
151
|
+
"""
|
|
152
|
+
for unit in ["B", "KB", "MB", "GB"]:
|
|
153
|
+
if size_bytes < 1024.0:
|
|
154
|
+
return f"{size_bytes:.1f} {unit}"
|
|
155
|
+
size_bytes /= 1024.0
|
|
156
|
+
return f"{size_bytes:.1f} TB"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def display_dataset_card(name: str, data: Any, metadata: Optional[Dict] = None):
|
|
160
|
+
"""Display a dataset summary card in Streamlit.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
name : str
|
|
165
|
+
Dataset name
|
|
166
|
+
data : Any
|
|
167
|
+
Dataset (DataFrame, ndarray, etc.)
|
|
168
|
+
metadata : dict, optional
|
|
169
|
+
Additional metadata to display
|
|
170
|
+
"""
|
|
171
|
+
with st.container():
|
|
172
|
+
st.subheader(f"📊 {name}")
|
|
173
|
+
|
|
174
|
+
if isinstance(data, pd.DataFrame):
|
|
175
|
+
info = get_dataframe_info(data)
|
|
176
|
+
|
|
177
|
+
col1, col2, col3 = st.columns(3)
|
|
178
|
+
col1.metric("Rows", info["shape"][0])
|
|
179
|
+
col2.metric("Columns", info["shape"][1])
|
|
180
|
+
col3.metric("Memory", format_data_size(info["memory_usage"]))
|
|
181
|
+
|
|
182
|
+
st.write("**Column Types:**")
|
|
183
|
+
dtypes_df = pd.DataFrame(
|
|
184
|
+
[{"Column": col, "Type": dtype} for col, dtype in info["dtypes"].items()]
|
|
185
|
+
)
|
|
186
|
+
st.dataframe(dtypes_df, hide_index=True, width="stretch")
|
|
187
|
+
|
|
188
|
+
# Show missing values if any
|
|
189
|
+
missing_total = sum(info["missing_values"].values())
|
|
190
|
+
if missing_total > 0:
|
|
191
|
+
st.warning(f"⚠️ {missing_total} missing values found")
|
|
192
|
+
|
|
193
|
+
elif isinstance(data, np.ndarray):
|
|
194
|
+
info = get_array_info(data)
|
|
195
|
+
|
|
196
|
+
col1, col2, col3 = st.columns(3)
|
|
197
|
+
col1.metric("Shape", str(info["shape"]))
|
|
198
|
+
col2.metric("Type", info["dtype"])
|
|
199
|
+
col3.metric("Memory", format_data_size(info["memory_usage"]))
|
|
200
|
+
|
|
201
|
+
if "mean" in info:
|
|
202
|
+
st.write("**Statistics:**")
|
|
203
|
+
col1, col2, col3, col4 = st.columns(4)
|
|
204
|
+
col1.metric("Min", f"{info['min']:.4g}")
|
|
205
|
+
col2.metric("Max", f"{info['max']:.4g}")
|
|
206
|
+
col3.metric("Mean", f"{info['mean']:.4g}")
|
|
207
|
+
col4.metric("Std Dev", f"{info['std']:.4g}")
|
|
208
|
+
|
|
209
|
+
else:
|
|
210
|
+
st.write(f"**Type:** {type(data).__name__}")
|
|
211
|
+
if hasattr(data, "__len__"):
|
|
212
|
+
st.write(f"**Length:** {len(data)}")
|
|
213
|
+
|
|
214
|
+
# Display additional metadata if provided
|
|
215
|
+
if metadata:
|
|
216
|
+
with st.expander("Additional Metadata"):
|
|
217
|
+
st.json(metadata)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def get_column_suggestions(df: pd.DataFrame) -> Dict[str, List[str]]:
|
|
221
|
+
"""Suggest numeric/categorical columns for plotting.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
df : pd.DataFrame
|
|
226
|
+
DataFrame to analyze
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
suggestions : dict
|
|
231
|
+
Dictionary with suggestions:
|
|
232
|
+
- x_candidates: columns suitable for X-axis
|
|
233
|
+
- y_candidates: columns suitable for Y-axis
|
|
234
|
+
- hue_candidates: columns suitable for grouping/color
|
|
235
|
+
"""
|
|
236
|
+
numeric_cols = list(df.select_dtypes(include=[np.number]).columns)
|
|
237
|
+
categorical_cols = list(df.select_dtypes(include=["object", "category"]).columns)
|
|
238
|
+
|
|
239
|
+
# For X-axis: prefer numeric or datetime
|
|
240
|
+
x_candidates = numeric_cols.copy()
|
|
241
|
+
|
|
242
|
+
# For Y-axis: numeric only
|
|
243
|
+
y_candidates = numeric_cols.copy()
|
|
244
|
+
|
|
245
|
+
# For hue/grouping: categorical or low-cardinality numeric
|
|
246
|
+
hue_candidates = categorical_cols.copy()
|
|
247
|
+
for col in numeric_cols:
|
|
248
|
+
if df[col].nunique() <= 10: # Low cardinality
|
|
249
|
+
hue_candidates.append(col)
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"x_candidates": x_candidates,
|
|
253
|
+
"y_candidates": y_candidates,
|
|
254
|
+
"hue_candidates": hue_candidates,
|
|
255
|
+
"numeric": numeric_cols,
|
|
256
|
+
"categorical": categorical_cols,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def display_data_preview(data: Any, name: str = "Data"):
|
|
261
|
+
"""Display an intelligent preview of any data type.
|
|
262
|
+
|
|
263
|
+
Parameters
|
|
264
|
+
----------
|
|
265
|
+
data : Any
|
|
266
|
+
Data to preview
|
|
267
|
+
name : str, default "Data"
|
|
268
|
+
Name to display in preview
|
|
269
|
+
"""
|
|
270
|
+
st.write(f"### {name} Preview")
|
|
271
|
+
|
|
272
|
+
if isinstance(data, pd.DataFrame):
|
|
273
|
+
st.write(f"**Shape:** {data.shape[0]} rows × {data.shape[1]} columns")
|
|
274
|
+
|
|
275
|
+
# Show preview
|
|
276
|
+
preview = preview_dataframe(data, n_rows=5)
|
|
277
|
+
st.dataframe(preview, width="stretch")
|
|
278
|
+
|
|
279
|
+
# Quick stats for numeric columns
|
|
280
|
+
numeric_cols = data.select_dtypes(include=[np.number]).columns
|
|
281
|
+
if len(numeric_cols) > 0:
|
|
282
|
+
with st.expander("Quick Statistics"):
|
|
283
|
+
st.dataframe(data[numeric_cols].describe(), width="stretch")
|
|
284
|
+
|
|
285
|
+
elif isinstance(data, np.ndarray):
|
|
286
|
+
info = get_array_info(data)
|
|
287
|
+
st.write(f"**Shape:** {info['shape']}, **Type:** {info['dtype']}")
|
|
288
|
+
|
|
289
|
+
# For 1D or 2D arrays, show a sample
|
|
290
|
+
if data.ndim == 1:
|
|
291
|
+
st.write("**First 10 elements:**")
|
|
292
|
+
st.write(data[:10])
|
|
293
|
+
elif data.ndim == 2:
|
|
294
|
+
st.write("**First 5 rows:**")
|
|
295
|
+
st.write(data[:5])
|
|
296
|
+
else:
|
|
297
|
+
st.write(f"**{data.ndim}D array** - too complex to preview directly")
|
|
298
|
+
|
|
299
|
+
elif isinstance(data, dict):
|
|
300
|
+
st.write(f"**Dictionary with {len(data)} keys:**")
|
|
301
|
+
st.json({k: str(v)[:100] for k, v in list(data.items())[:10]})
|
|
302
|
+
|
|
303
|
+
elif isinstance(data, (list, tuple)):
|
|
304
|
+
st.write(f"**{type(data).__name__} with {len(data)} elements:**")
|
|
305
|
+
st.write(data[:10] if len(data) > 10 else data)
|
|
306
|
+
|
|
307
|
+
else:
|
|
308
|
+
st.write(f"**Type:** {type(data).__name__}")
|
|
309
|
+
st.write(data)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def get_plottable_arrays(data: Any) -> Tuple[Optional[np.ndarray], Optional[List[str]]]:
|
|
313
|
+
"""Extract plottable arrays from various data types.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
data : Any
|
|
318
|
+
Input data (DataFrame, ndarray, dict, etc.)
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
arrays : np.ndarray or None
|
|
323
|
+
Extracted numeric arrays, or None if not applicable
|
|
324
|
+
labels : list of str or None
|
|
325
|
+
Column/array labels, or None
|
|
326
|
+
"""
|
|
327
|
+
if isinstance(data, pd.DataFrame):
|
|
328
|
+
numeric_cols = data.select_dtypes(include=[np.number]).columns
|
|
329
|
+
if len(numeric_cols) > 0:
|
|
330
|
+
return data[numeric_cols].values, list(numeric_cols)
|
|
331
|
+
return None, None
|
|
332
|
+
|
|
333
|
+
elif isinstance(data, np.ndarray):
|
|
334
|
+
if np.issubdtype(data.dtype, np.number):
|
|
335
|
+
return data, None
|
|
336
|
+
return None, None
|
|
337
|
+
|
|
338
|
+
elif isinstance(data, dict):
|
|
339
|
+
# Try to extract numeric arrays from dict
|
|
340
|
+
numeric_items = {
|
|
341
|
+
k: v
|
|
342
|
+
for k, v in data.items()
|
|
343
|
+
if isinstance(v, (np.ndarray, list))
|
|
344
|
+
and len(v) > 0
|
|
345
|
+
and isinstance(v[0] if isinstance(v, list) else v.flat[0], (int, float, np.number))
|
|
346
|
+
}
|
|
347
|
+
if numeric_items:
|
|
348
|
+
arrays = np.column_stack(list(numeric_items.values()))
|
|
349
|
+
return arrays, list(numeric_items.keys())
|
|
350
|
+
return None, None
|
|
351
|
+
|
|
352
|
+
return None, None
|