AeroViz 0.1.3__py3-none-any.whl → 0.1.4__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.
Potentially problematic release.
This version of AeroViz might be problematic. Click here for more details.
- AeroViz/__init__.py +7 -5
- AeroViz/{config → data}/DEFAULT_DATA.csv +1 -1
- AeroViz/dataProcess/Chemistry/__init__.py +40 -40
- AeroViz/dataProcess/Chemistry/_calculate.py +15 -15
- AeroViz/dataProcess/Chemistry/_isoropia.py +72 -68
- AeroViz/dataProcess/Chemistry/_mass_volume.py +158 -161
- AeroViz/dataProcess/Chemistry/_ocec.py +109 -109
- AeroViz/dataProcess/Chemistry/_partition.py +19 -18
- AeroViz/dataProcess/Chemistry/_teom.py +9 -11
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Optical/Angstrom_exponent.py +20 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +40 -41
- AeroViz/dataProcess/Optical/__init__.py +29 -44
- AeroViz/dataProcess/Optical/_absorption.py +21 -47
- AeroViz/dataProcess/Optical/_extinction.py +31 -25
- AeroViz/dataProcess/Optical/_mie.py +5 -7
- AeroViz/dataProcess/Optical/_mie_sd.py +89 -90
- AeroViz/dataProcess/Optical/_scattering.py +19 -20
- AeroViz/dataProcess/SizeDistr/__init__.py +39 -39
- AeroViz/dataProcess/SizeDistr/__merge.py +159 -158
- AeroViz/dataProcess/SizeDistr/_merge.py +155 -154
- AeroViz/dataProcess/SizeDistr/_merge_v1.py +162 -161
- AeroViz/dataProcess/SizeDistr/_merge_v2.py +153 -152
- AeroViz/dataProcess/SizeDistr/_merge_v3.py +327 -327
- AeroViz/dataProcess/SizeDistr/_merge_v4.py +273 -275
- AeroViz/dataProcess/SizeDistr/_size_distr.py +51 -51
- AeroViz/dataProcess/VOC/__init__.py +9 -9
- AeroViz/dataProcess/VOC/_potential_par.py +53 -55
- AeroViz/dataProcess/__init__.py +28 -6
- AeroViz/dataProcess/core/__init__.py +59 -65
- AeroViz/plot/__init__.py +7 -2
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +69 -0
- AeroViz/plot/distribution/distribution.py +421 -427
- AeroViz/plot/meteorology/meteorology.py +240 -292
- AeroViz/plot/optical/__init__.py +0 -1
- AeroViz/plot/optical/optical.py +230 -230
- AeroViz/plot/pie.py +198 -0
- AeroViz/plot/regression.py +196 -0
- AeroViz/plot/scatter.py +165 -0
- AeroViz/plot/templates/__init__.py +2 -4
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/contour.py +25 -25
- AeroViz/plot/templates/corr_matrix.py +86 -93
- AeroViz/plot/templates/diurnal_pattern.py +28 -26
- AeroViz/plot/templates/koschmieder.py +59 -123
- AeroViz/plot/templates/metal_heatmap.py +135 -37
- AeroViz/plot/timeseries/__init__.py +1 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +324 -264
- AeroViz/plot/utils/__init__.py +2 -1
- AeroViz/plot/utils/_color.py +57 -57
- AeroViz/plot/utils/_unit.py +48 -48
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +5 -0
- AeroViz/plot/violin.py +80 -0
- AeroViz/process/__init__.py +17 -17
- AeroViz/process/core/DataProc.py +9 -9
- AeroViz/process/core/SizeDist.py +81 -81
- AeroViz/process/method/PyMieScatt_update.py +488 -488
- AeroViz/process/method/mie_theory.py +231 -229
- AeroViz/process/method/prop.py +40 -40
- AeroViz/process/script/AbstractDistCalc.py +103 -103
- AeroViz/process/script/Chemical.py +168 -167
- AeroViz/process/script/IMPACT.py +40 -40
- AeroViz/process/script/IMPROVE.py +152 -152
- AeroViz/process/script/Others.py +45 -45
- AeroViz/process/script/PSD.py +26 -26
- AeroViz/process/script/PSD_dry.py +69 -70
- AeroViz/process/script/retrieve_RI.py +50 -51
- AeroViz/rawDataReader/__init__.py +53 -58
- AeroViz/rawDataReader/config/supported_instruments.py +155 -0
- AeroViz/rawDataReader/core/__init__.py +233 -356
- AeroViz/rawDataReader/script/AE33.py +17 -18
- AeroViz/rawDataReader/script/AE43.py +18 -21
- AeroViz/rawDataReader/script/APS_3321.py +30 -30
- AeroViz/rawDataReader/script/Aurora.py +23 -24
- AeroViz/rawDataReader/script/BC1054.py +36 -40
- AeroViz/rawDataReader/script/EPA_vertical.py +37 -9
- AeroViz/rawDataReader/script/GRIMM.py +16 -23
- AeroViz/rawDataReader/script/IGAC.py +90 -0
- AeroViz/rawDataReader/script/MA350.py +32 -39
- AeroViz/rawDataReader/script/Minion.py +103 -0
- AeroViz/rawDataReader/script/NEPH.py +69 -74
- AeroViz/rawDataReader/script/SMPS_TH.py +25 -25
- AeroViz/rawDataReader/script/SMPS_aim11.py +32 -32
- AeroViz/rawDataReader/script/SMPS_genr.py +31 -31
- AeroViz/rawDataReader/script/Sunset_OCEC.py +60 -0
- AeroViz/rawDataReader/script/TEOM.py +30 -28
- AeroViz/rawDataReader/script/Table.py +13 -14
- AeroViz/rawDataReader/script/VOC.py +26 -0
- AeroViz/rawDataReader/script/__init__.py +18 -20
- AeroViz/tools/database.py +64 -66
- AeroViz/tools/dataclassifier.py +106 -106
- AeroViz/tools/dataprinter.py +51 -51
- AeroViz/tools/datareader.py +38 -38
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/METADATA +5 -4
- AeroViz-0.1.4.dist-info/RECORD +112 -0
- AeroViz/plot/improve/__init__.py +0 -1
- AeroViz/plot/improve/improve.py +0 -240
- AeroViz/plot/optical/aethalometer.py +0 -77
- AeroViz/plot/templates/event_evolution.py +0 -65
- AeroViz/plot/templates/regression.py +0 -256
- AeroViz/plot/templates/scatter.py +0 -130
- AeroViz/plot/templates/templates.py +0 -398
- AeroViz/plot/utils/_decorator.py +0 -74
- AeroViz/rawDataReader/script/IGAC_TH.py +0 -104
- AeroViz/rawDataReader/script/IGAC_ZM.py +0 -90
- AeroViz/rawDataReader/script/OCEC_LCRES.py +0 -34
- AeroViz/rawDataReader/script/OCEC_RES.py +0 -28
- AeroViz/rawDataReader/script/VOC_TH.py +0 -30
- AeroViz/rawDataReader/script/VOC_ZM.py +0 -37
- AeroViz/rawDataReader/utils/__init__.py +0 -0
- AeroViz/rawDataReader/utils/config.py +0 -169
- AeroViz-0.1.3.dist-info/RECORD +0 -111
- /AeroViz/{config → data}/DEFAULT_PNSD_DATA.csv +0 -0
- /AeroViz/{config → rawDataReader/config}/__init__.py +0 -0
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/LICENSE +0 -0
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/WHEEL +0 -0
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/top_level.txt +0 -0
AeroViz/plot/pie.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from matplotlib.pyplot import Figure, Axes
|
|
7
|
+
from pandas import DataFrame
|
|
8
|
+
|
|
9
|
+
from AeroViz.plot.utils import *
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'pie',
|
|
13
|
+
'donuts'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@set_figure(fw='bold')
|
|
18
|
+
def pie(data_set: DataFrame | dict,
|
|
19
|
+
labels: list[str],
|
|
20
|
+
unit: str,
|
|
21
|
+
style: Literal["pie", 'donut'],
|
|
22
|
+
ax: Axes | None = None,
|
|
23
|
+
symbol: bool = True,
|
|
24
|
+
**kwargs) -> tuple[Figure, Axes]:
|
|
25
|
+
"""
|
|
26
|
+
Create a pie or donut chart based on the provided data.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
data_set : pd.DataFrame | dict
|
|
31
|
+
A pandas DataFrame or dictionary mapping category names to a list of species.
|
|
32
|
+
If a DataFrame is provided, the index represents the categories, and each column contains species data.
|
|
33
|
+
If a dictionary is provided, it maps category names to lists of species data.
|
|
34
|
+
It is assumed that all lists or DataFrame columns contain the same number of entries as the *labels* list.
|
|
35
|
+
labels : list of str
|
|
36
|
+
The labels for each category.
|
|
37
|
+
unit : str
|
|
38
|
+
The unit to display in the center of the donut chart.
|
|
39
|
+
style : Literal["pie", 'donut']
|
|
40
|
+
The style of the chart, either 'pie' for a standard pie chart or 'donut' for a donut chart.
|
|
41
|
+
ax : plt.Axes or None, optional
|
|
42
|
+
The Axes object to plot the chart onto. If None, a new figure and Axes will be created.
|
|
43
|
+
symbol : bool, optional
|
|
44
|
+
Whether to display values for each species in the chart.
|
|
45
|
+
**kwargs
|
|
46
|
+
Additional keyword arguments to be passed to the plotting function.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
matplotlib.axes.Axes
|
|
51
|
+
The Axes object containing the violin plot.
|
|
52
|
+
|
|
53
|
+
Notes
|
|
54
|
+
-----
|
|
55
|
+
- If *data_set* is a dictionary, it should contain lists of species that correspond to each category in *labels*.
|
|
56
|
+
- The length of each list in *data_set* or the number of columns in the DataFrame should match the length of the *labels* list.
|
|
57
|
+
|
|
58
|
+
Examples
|
|
59
|
+
--------
|
|
60
|
+
>>> data_set = {'Category 1': [10, 20, 30], 'Category 2': [15, 25, 35]}
|
|
61
|
+
>>> labels = ['Species 1', 'Species 2', 'Species 3']
|
|
62
|
+
>>> pie(data_set, labels, unit='kg', style='pie', symbol=True)
|
|
63
|
+
"""
|
|
64
|
+
if isinstance(data_set, DataFrame):
|
|
65
|
+
category_names = list(data_set.index)
|
|
66
|
+
data = data_set.to_numpy()
|
|
67
|
+
|
|
68
|
+
pies, species = data.shape
|
|
69
|
+
|
|
70
|
+
elif isinstance(data_set, dict):
|
|
71
|
+
category_names = list(data_set.keys())
|
|
72
|
+
data = np.array(list(data_set.values()))
|
|
73
|
+
|
|
74
|
+
pies, species = data.shape
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError('data_set must be a DataFrame or a dictionary.')
|
|
78
|
+
|
|
79
|
+
colors = kwargs.get('colors') or (Color.colors1 if species == 6 else Color.getColor(num=species))
|
|
80
|
+
|
|
81
|
+
radius = 4
|
|
82
|
+
width = 4 if style == 'pie' else 1
|
|
83
|
+
|
|
84
|
+
text = [''] * pies if style == 'pie' else [Unit(unit) + '\n\n' + '{:.2f}'.format(x) for x in data.sum(axis=1)]
|
|
85
|
+
pct_distance = 0.6 if style == 'pie' else 0.88
|
|
86
|
+
|
|
87
|
+
fig, ax = plt.subplots(1, pies, figsize=((pies * 2) + 1, 2)) if ax is None else (ax.get_figure(), ax)
|
|
88
|
+
|
|
89
|
+
if pies == 1:
|
|
90
|
+
ax = [ax]
|
|
91
|
+
|
|
92
|
+
for i in range(pies):
|
|
93
|
+
ax[i].pie(data[i], labels=None, colors=colors, textprops=None,
|
|
94
|
+
autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
|
|
95
|
+
pctdistance=pct_distance, radius=radius, wedgeprops=dict(width=width, edgecolor='w'))
|
|
96
|
+
|
|
97
|
+
ax[i].pie(data[i], labels=None, colors=colors, textprops=None,
|
|
98
|
+
autopct=lambda pct: auto_label_pct(pct, symbol=symbol, ignore='outer', include_pct=True),
|
|
99
|
+
pctdistance=1.3, radius=radius, wedgeprops=dict(width=width, edgecolor='w'))
|
|
100
|
+
ax[i].axis('equal')
|
|
101
|
+
ax[i].text(0, 0, text[i], ha='center', va='center')
|
|
102
|
+
ax[i].set_title(category_names[i])
|
|
103
|
+
|
|
104
|
+
ax[-1].legend(labels, loc='center left', prop={'size': 8, 'weight': 'normal'}, bbox_to_anchor=(1, 0, 1.15, 1))
|
|
105
|
+
|
|
106
|
+
# fig.savefig(f"pie_{style}_{title}")
|
|
107
|
+
|
|
108
|
+
plt.show()
|
|
109
|
+
|
|
110
|
+
return fig, ax
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@set_figure(fw='bold')
|
|
114
|
+
def donuts(data_set: DataFrame | dict,
|
|
115
|
+
labels: list[str],
|
|
116
|
+
unit: str,
|
|
117
|
+
ax: Axes | None = None,
|
|
118
|
+
symbol=True,
|
|
119
|
+
**kwargs) -> tuple[Figure, Axes]:
|
|
120
|
+
"""
|
|
121
|
+
Plot a donut chart based on the data set.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
data_set : pd.DataFrame | dict
|
|
126
|
+
A pandas DataFrame or a dictionary mapping category names to a list of species.
|
|
127
|
+
If a DataFrame is provided, the index represents the categories, and each column contains species data.
|
|
128
|
+
If a dictionary is provided, it maps category names to lists of species data.
|
|
129
|
+
It is assumed that all lists or DataFrame columns contain the same number of entries as the *labels* list.
|
|
130
|
+
labels : list of str
|
|
131
|
+
The category labels.
|
|
132
|
+
unit : str
|
|
133
|
+
The unit to be displayed in the center of the donut chart.
|
|
134
|
+
ax : matplotlib.axes.Axes, optional
|
|
135
|
+
The axes to plot on. If None, the current axes will be used (default).
|
|
136
|
+
symbol : bool, optional
|
|
137
|
+
Whether to display values for each species (default is True).
|
|
138
|
+
**kwargs : dict, optional
|
|
139
|
+
Additional keyword arguments to pass to the matplotlib pie chart function.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
matplotlib.axes.Axes
|
|
144
|
+
The axes containing the donut chart.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
if isinstance(data_set, DataFrame):
|
|
148
|
+
category_names = list(data_set.index)
|
|
149
|
+
data = data_set.to_numpy()
|
|
150
|
+
|
|
151
|
+
pies, species = data.shape
|
|
152
|
+
|
|
153
|
+
elif isinstance(data_set, dict):
|
|
154
|
+
category_names = list(data_set.keys())
|
|
155
|
+
data = np.array(list(data_set.values()))
|
|
156
|
+
|
|
157
|
+
pies, species = data.shape
|
|
158
|
+
|
|
159
|
+
else:
|
|
160
|
+
raise ValueError('data_set must be a DataFrame or a dictionary.')
|
|
161
|
+
|
|
162
|
+
colors1 = kwargs.get('colors') or (Color.colors1 if species == 6 else Color.getColor(num=species))
|
|
163
|
+
colors2 = Color.adjust_opacity(colors1, 0.8)
|
|
164
|
+
colors3 = Color.adjust_opacity(colors1, 0.6)
|
|
165
|
+
|
|
166
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
167
|
+
|
|
168
|
+
ax.pie(data[2], labels=None, colors=colors1, textprops=None,
|
|
169
|
+
autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
|
|
170
|
+
pctdistance=0.9, radius=14, wedgeprops=dict(width=3, edgecolor='w'))
|
|
171
|
+
|
|
172
|
+
ax.pie(data[1], labels=None, colors=colors2, textprops=None,
|
|
173
|
+
autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
|
|
174
|
+
pctdistance=0.85, radius=11, wedgeprops=dict(width=3, edgecolor='w'))
|
|
175
|
+
|
|
176
|
+
ax.pie(data[0], labels=None, colors=colors3, textprops=None,
|
|
177
|
+
autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
|
|
178
|
+
pctdistance=0.80, radius=8, wedgeprops=dict(width=3, edgecolor='w'))
|
|
179
|
+
|
|
180
|
+
text = (Unit(f'{unit}') + '\n\n' +
|
|
181
|
+
'Event : ' + "{:.2f}".format(np.sum(data[2])) + '\n' +
|
|
182
|
+
'Transition : ' + "{:.2f}".format(np.sum(data[1])) + '\n' +
|
|
183
|
+
'Clean : ' + "{:.2f}".format(np.sum(data[0])))
|
|
184
|
+
|
|
185
|
+
ax.text(0, 0, text, ha='center', va='center')
|
|
186
|
+
ax.axis('equal')
|
|
187
|
+
|
|
188
|
+
ax.set_title(kwargs.get('title', ''))
|
|
189
|
+
|
|
190
|
+
ax.legend(labels, loc='center', prop={'size': 8}, title_fontproperties={'weight': 'bold'},
|
|
191
|
+
title=f'Outer : {category_names[2]}' + '\n' + f'Middle : {category_names[1]}' + '\n' + f'Inner : {category_names[0]}',
|
|
192
|
+
bbox_to_anchor=(0.8, 0, 0.5, 1))
|
|
193
|
+
|
|
194
|
+
# fig.savefig(f"donuts_{title}")
|
|
195
|
+
|
|
196
|
+
plt.show()
|
|
197
|
+
|
|
198
|
+
return fig, ax
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from matplotlib.pyplot import Figure, Axes
|
|
5
|
+
|
|
6
|
+
from AeroViz.plot.utils import *
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'linear_regression',
|
|
10
|
+
'multiple_linear_regression',
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@set_figure
|
|
15
|
+
def linear_regression(df: pd.DataFrame,
|
|
16
|
+
x: str | list[str],
|
|
17
|
+
y: str | list[str],
|
|
18
|
+
labels: str | list[str] = None,
|
|
19
|
+
ax: Axes | None = None,
|
|
20
|
+
diagonal=False,
|
|
21
|
+
positive: bool = True,
|
|
22
|
+
fit_intercept: bool = True,
|
|
23
|
+
**kwargs
|
|
24
|
+
) -> tuple[Figure, Axes]:
|
|
25
|
+
"""
|
|
26
|
+
Create a scatter plot with regression lines for the given data.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
df : pd.DataFrame
|
|
31
|
+
Input DataFrame containing the data.
|
|
32
|
+
x : str or list of str
|
|
33
|
+
Column name(s) for the x-axis variable(s). If a list, only the first element is used.
|
|
34
|
+
y : str or list of str
|
|
35
|
+
Column name(s) for the y-axis variable(s).
|
|
36
|
+
labels : str or list of str, optional
|
|
37
|
+
Labels for the y-axis variable(s). If None, column names are used as labels. Default is None.
|
|
38
|
+
ax : Axes, optional
|
|
39
|
+
Matplotlib Axes object to use for the plot. If None, a new subplot is created. Default is None.
|
|
40
|
+
diagonal : bool, optional
|
|
41
|
+
If True, a diagonal line (1:1 line) is added to the plot. Default is False.
|
|
42
|
+
positive : bool, optional
|
|
43
|
+
Whether to constrain the regression coefficients to be positive. Default is True.
|
|
44
|
+
fit_intercept: bool, optional
|
|
45
|
+
Whether to calculate the intercept for this model. Default is True.
|
|
46
|
+
**kwargs
|
|
47
|
+
Additional keyword arguments for plot customization.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
fig : Figure
|
|
52
|
+
The matplotlib Figure object.
|
|
53
|
+
ax : Axes
|
|
54
|
+
The matplotlib Axes object with the scatter plot.
|
|
55
|
+
|
|
56
|
+
Notes
|
|
57
|
+
-----
|
|
58
|
+
- The function creates a scatter plot with optional regression lines.
|
|
59
|
+
- The regression line is fitted for each y variable.
|
|
60
|
+
- Customization options are provided via **kwargs.
|
|
61
|
+
|
|
62
|
+
Example
|
|
63
|
+
-------
|
|
64
|
+
>>> linear_regression(df, x='X', y=['Y1', 'Y2'], labels=['Label1', 'Label2'],
|
|
65
|
+
... diagonal=True, xlim=(0, 10), ylim=(0, 20),
|
|
66
|
+
... xlabel="X-axis", ylabel="Y-axis", title="Scatter Plot with Regressions")
|
|
67
|
+
"""
|
|
68
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
69
|
+
|
|
70
|
+
if not isinstance(x, str):
|
|
71
|
+
x = x[0]
|
|
72
|
+
|
|
73
|
+
if not isinstance(y, list):
|
|
74
|
+
y = [y]
|
|
75
|
+
|
|
76
|
+
if labels is None:
|
|
77
|
+
labels = y
|
|
78
|
+
|
|
79
|
+
df = df.dropna(subset=[x, *y])
|
|
80
|
+
x_array = df[[x]].to_numpy()
|
|
81
|
+
|
|
82
|
+
color_cycle = Color.linecolor
|
|
83
|
+
|
|
84
|
+
handles, text_list = [], []
|
|
85
|
+
|
|
86
|
+
for i, y_var in enumerate(y):
|
|
87
|
+
y_array = df[[y_var]].to_numpy()
|
|
88
|
+
|
|
89
|
+
color = color_cycle[i % len(color_cycle)]
|
|
90
|
+
|
|
91
|
+
scatter = ax.scatter(x_array, y_array, s=25, color=color['face'], edgecolors=color['edge'], alpha=0.8,
|
|
92
|
+
label=labels[i])
|
|
93
|
+
handles.append(scatter)
|
|
94
|
+
|
|
95
|
+
text, y_predict, slope = linear_regression_base(x_array, y_array,
|
|
96
|
+
columns=labels[i],
|
|
97
|
+
positive=positive,
|
|
98
|
+
fit_intercept=fit_intercept)
|
|
99
|
+
|
|
100
|
+
text_list.append(f'{labels[i]}:\n{text}')
|
|
101
|
+
plt.plot(x_array, y_predict, linewidth=3, color=color['line'], alpha=1, zorder=3)
|
|
102
|
+
|
|
103
|
+
ax.set(xlim=kwargs.get('xlim'), ylim=kwargs.get('ylim'), xlabel=Unit(x), ylabel=Unit(y[0]),
|
|
104
|
+
title=kwargs.get('title'))
|
|
105
|
+
|
|
106
|
+
# Add regression info to the legend
|
|
107
|
+
leg = plt.legend(handles=handles, labels=text_list, loc='upper left', prop={'weight': 'bold'})
|
|
108
|
+
|
|
109
|
+
for text, color in zip(leg.get_texts(), [color['line'] for color in color_cycle]):
|
|
110
|
+
text.set_color(color)
|
|
111
|
+
|
|
112
|
+
if diagonal:
|
|
113
|
+
ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
|
|
114
|
+
plt.text(0.97, 0.97, r'$\bf 1:1\ Line$', color='k', ha='right', va='top', transform=ax.transAxes)
|
|
115
|
+
|
|
116
|
+
plt.show()
|
|
117
|
+
|
|
118
|
+
return fig, ax
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@set_figure
|
|
122
|
+
def multiple_linear_regression(df: pd.DataFrame,
|
|
123
|
+
x: str | list[str],
|
|
124
|
+
y: str | list[str],
|
|
125
|
+
labels: str | list[str] = None,
|
|
126
|
+
ax: Axes | None = None,
|
|
127
|
+
diagonal=False,
|
|
128
|
+
positive: bool = True,
|
|
129
|
+
fit_intercept: bool = True,
|
|
130
|
+
**kwargs
|
|
131
|
+
) -> tuple[Figure, Axes]:
|
|
132
|
+
"""
|
|
133
|
+
Perform multiple linear regression analysis and plot the results.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
df : pd.DataFrame
|
|
138
|
+
Input DataFrame containing the data.
|
|
139
|
+
x : str or list of str
|
|
140
|
+
Column name(s) for the independent variable(s). Can be a single string or a list of strings.
|
|
141
|
+
y : str or list of str
|
|
142
|
+
Column name(s) for the dependent variable(s). Can be a single string or a list of strings.
|
|
143
|
+
labels : str or list of str, optional
|
|
144
|
+
Labels for the dependent variable(s). If None, column names are used as labels. Default is None.
|
|
145
|
+
ax : Axes, optional
|
|
146
|
+
Matplotlib Axes object to use for the plot. If None, a new subplot is created. Default is None.
|
|
147
|
+
diagonal : bool, optional
|
|
148
|
+
Whether to include a diagonal line (1:1 line) in the plot. Default is False.
|
|
149
|
+
positive : bool, optional
|
|
150
|
+
Whether to constrain the regression coefficients to be positive. Default is True.
|
|
151
|
+
fit_intercept: bool, optional
|
|
152
|
+
Whether to calculate the intercept for this model. Default is True.
|
|
153
|
+
**kwargs
|
|
154
|
+
Additional keyword arguments for plot customization.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
tuple[Figure, Axes]
|
|
159
|
+
The Figure and Axes containing the regression plot.
|
|
160
|
+
|
|
161
|
+
Notes
|
|
162
|
+
-----
|
|
163
|
+
This function performs multiple linear regression analysis using the input DataFrame.
|
|
164
|
+
It supports multiple independent variables and can plot the regression results.
|
|
165
|
+
|
|
166
|
+
Example
|
|
167
|
+
-------
|
|
168
|
+
>>> multiple_linear_regression(df, x=['X1', 'X2'], y='Y', labels=['Y1', 'Y2'],
|
|
169
|
+
... diagonal=True, fit_intercept=True,
|
|
170
|
+
... xlabel="X-axis", ylabel="Y-axis", title="Multiple Linear Regression Plot")
|
|
171
|
+
"""
|
|
172
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
173
|
+
|
|
174
|
+
if not isinstance(x, list):
|
|
175
|
+
x = [x]
|
|
176
|
+
|
|
177
|
+
if not isinstance(y, str):
|
|
178
|
+
y = y[0]
|
|
179
|
+
|
|
180
|
+
if labels is None:
|
|
181
|
+
labels = x
|
|
182
|
+
|
|
183
|
+
df = df[[*x, y]].dropna()
|
|
184
|
+
x_array = df[[*x]].to_numpy()
|
|
185
|
+
y_array = df[[y]].to_numpy()
|
|
186
|
+
|
|
187
|
+
text, y_predict, coefficients = linear_regression_base(x_array, y_array,
|
|
188
|
+
columns=labels,
|
|
189
|
+
positive=positive,
|
|
190
|
+
fit_intercept=fit_intercept)
|
|
191
|
+
|
|
192
|
+
df = pd.DataFrame(np.concatenate([y_array, y_predict], axis=1), columns=['y_actual', 'y_predict'])
|
|
193
|
+
|
|
194
|
+
linear_regression(df, x='y_actual', y='y_predict', ax=ax, regression=True, diagonal=diagonal)
|
|
195
|
+
|
|
196
|
+
return fig, ax
|
AeroViz/plot/scatter.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import seaborn as sns
|
|
5
|
+
from matplotlib.colors import Normalize
|
|
6
|
+
from matplotlib.pyplot import Figure, Axes
|
|
7
|
+
from matplotlib.ticker import ScalarFormatter
|
|
8
|
+
|
|
9
|
+
from AeroViz.plot.utils import *
|
|
10
|
+
|
|
11
|
+
__all__ = ['scatter']
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def check_empty(*arrays):
|
|
15
|
+
for i, arr in enumerate(arrays):
|
|
16
|
+
if arr.size == 0:
|
|
17
|
+
raise ValueError(f"Array is empty!")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@set_figure
|
|
21
|
+
def scatter(df: pd.DataFrame,
|
|
22
|
+
x: str,
|
|
23
|
+
y: str,
|
|
24
|
+
c: str | None = None,
|
|
25
|
+
s: str | None = None,
|
|
26
|
+
cmap='jet',
|
|
27
|
+
regression=False,
|
|
28
|
+
diagonal=False,
|
|
29
|
+
ax: Axes | None = None,
|
|
30
|
+
**kwargs
|
|
31
|
+
) -> tuple[Figure, Axes]:
|
|
32
|
+
"""
|
|
33
|
+
Creates a scatter plot with optional color and size encoding.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
df : pd.DataFrame
|
|
38
|
+
The DataFrame containing the data to plot.
|
|
39
|
+
x : str
|
|
40
|
+
The column name for the x-axis values.
|
|
41
|
+
y : str
|
|
42
|
+
The column name for the y-axis values.
|
|
43
|
+
c : str, optional
|
|
44
|
+
The column name for color encoding. Default is None.
|
|
45
|
+
s : str, optional
|
|
46
|
+
The column name for size encoding. Default is None.
|
|
47
|
+
cmap : str, optional
|
|
48
|
+
The colormap to use for the color encoding. Default is 'jet'.
|
|
49
|
+
regression : bool, optional
|
|
50
|
+
If True, fits and plots a linear regression line. Default is False.
|
|
51
|
+
diagonal : bool, optional
|
|
52
|
+
If True, plots a 1:1 diagonal line. Default is False.
|
|
53
|
+
ax : Axes, optional
|
|
54
|
+
The matplotlib Axes to plot on. If not provided, a new figure and axes are created.
|
|
55
|
+
**kwargs : Any
|
|
56
|
+
Additional keyword arguments passed to customize the plot, such as `fig_kws` for figure creation and `xlabel`,
|
|
57
|
+
`ylabel`, `xlim`, `ylim`, `title` for axis labeling and limits.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
fig : Figure
|
|
62
|
+
The matplotlib Figure object.
|
|
63
|
+
ax : Axes
|
|
64
|
+
The matplotlib Axes object with the scatter plot.
|
|
65
|
+
|
|
66
|
+
Notes
|
|
67
|
+
-----
|
|
68
|
+
- If both `c` and `s` are provided, the scatter plot will encode data points using both color and size.
|
|
69
|
+
- If only `c` is provided, data points will be color-coded according to the values in the `c` column.
|
|
70
|
+
- If only `s` is provided, data points will be sized according to the values in the `s` column.
|
|
71
|
+
- If neither `c` nor `s` is provided, a basic scatter plot is created.
|
|
72
|
+
- The `regression` option will add a linear regression line and display the equation on the plot.
|
|
73
|
+
- The `diagonal` option will add a 1:1 reference line to the plot.
|
|
74
|
+
|
|
75
|
+
Examples
|
|
76
|
+
--------
|
|
77
|
+
>>> import pandas as pd
|
|
78
|
+
>>> from AeroViz.plot import scatter
|
|
79
|
+
>>> df = pd.DataFrame({
|
|
80
|
+
>>> 'x': [1, 2, 3, 4],
|
|
81
|
+
>>> 'y': [1.1, 2.0, 2.9, 4.1],
|
|
82
|
+
>>> 'color': [10, 20, 30, 40],
|
|
83
|
+
>>> 'size': [100, 200, 300, 400]
|
|
84
|
+
>>> })
|
|
85
|
+
>>> fig, ax = scatter(df, x='x', y='y', c='color', s='size', regression=True, diagonal=True)
|
|
86
|
+
"""
|
|
87
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
88
|
+
|
|
89
|
+
if c is not None and s is not None:
|
|
90
|
+
df_ = df.dropna(subset=[x, y, c, s]).copy()
|
|
91
|
+
x_data, y_data, c_data, s_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[c].to_numpy(), df_[s].to_numpy()
|
|
92
|
+
check_empty(x_data, y_data, c_data, s_data)
|
|
93
|
+
|
|
94
|
+
scatter = ax.scatter(x_data, y_data, c=c_data,
|
|
95
|
+
norm=Normalize(vmin=np.percentile(c_data, 10), vmax=np.percentile(c_data, 90)),
|
|
96
|
+
cmap=cmap, s=50 * (s_data / s_data.max()) ** 1.5, alpha=0.7, edgecolors=None)
|
|
97
|
+
colorbar = True
|
|
98
|
+
|
|
99
|
+
dot = np.linspace(s_data.min(), s_data.max(), 6).round(-1)
|
|
100
|
+
|
|
101
|
+
for dott in dot[1:-1]:
|
|
102
|
+
plt.scatter([], [], c='k', alpha=0.8, s=50 * (dott / s_data.max()) ** 1.5, label='{:.0f}'.format(dott))
|
|
103
|
+
|
|
104
|
+
plt.legend(title=Unit(s))
|
|
105
|
+
|
|
106
|
+
elif c is not None:
|
|
107
|
+
df_ = df.dropna(subset=[x, y, c]).copy()
|
|
108
|
+
x_data, y_data, c_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[c].to_numpy()
|
|
109
|
+
check_empty(x_data, y_data, c_data)
|
|
110
|
+
|
|
111
|
+
scatter = ax.scatter(x_data, y_data, c=c_data, vmin=c_data.min(), vmax=np.percentile(c_data, 90), cmap=cmap,
|
|
112
|
+
alpha=0.7,
|
|
113
|
+
edgecolors=None)
|
|
114
|
+
colorbar = True
|
|
115
|
+
|
|
116
|
+
elif s is not None:
|
|
117
|
+
df_ = df.dropna(subset=[x, y, s]).copy()
|
|
118
|
+
x_data, y_data, s_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[s].to_numpy()
|
|
119
|
+
check_empty(x_data, y_data, s_data)
|
|
120
|
+
|
|
121
|
+
scatter = ax.scatter(x_data, y_data, s=50 * (s_data / s_data.max()) ** 1.5, color='#7a97c9', alpha=0.7,
|
|
122
|
+
edgecolors='white')
|
|
123
|
+
colorbar = False
|
|
124
|
+
|
|
125
|
+
# dealing
|
|
126
|
+
dot = np.linspace(s_data.min(), s_data.max(), 6).round(-1)
|
|
127
|
+
|
|
128
|
+
for dott in dot[1:-1]:
|
|
129
|
+
plt.scatter([], [], c='k', alpha=0.8, s=50 * (dott / s_data.max()) ** 1.5, label='{:.0f}'.format(dott))
|
|
130
|
+
|
|
131
|
+
plt.legend(title=Unit(s))
|
|
132
|
+
|
|
133
|
+
else:
|
|
134
|
+
df_ = df.dropna(subset=[x, y]).copy()
|
|
135
|
+
x_data, y_data = df_[x].to_numpy(), df_[y].to_numpy()
|
|
136
|
+
check_empty(x_data, y_data)
|
|
137
|
+
|
|
138
|
+
scatter = ax.scatter(x_data, y_data, s=30, color='#7a97c9', alpha=0.7, edgecolors='white')
|
|
139
|
+
colorbar = False
|
|
140
|
+
|
|
141
|
+
ax.set(xlim=kwargs.get('xlim', (x_data.min(), x_data.max())),
|
|
142
|
+
ylim=kwargs.get('ylim', (y_data.min(), y_data.max())),
|
|
143
|
+
xlabel=kwargs.get('xlabel', Unit(x)),
|
|
144
|
+
ylabel=kwargs.get('ylabel', Unit(y)),
|
|
145
|
+
title=kwargs.get('title', ''))
|
|
146
|
+
|
|
147
|
+
if colorbar:
|
|
148
|
+
plt.colorbar(scatter, extend='both', label=Unit(c))
|
|
149
|
+
|
|
150
|
+
if regression:
|
|
151
|
+
text, y_predict, slope = linear_regression_base(x_data, y_data)
|
|
152
|
+
ax.plot(x_data, y_predict, linewidth=3, color=sns.xkcd_rgb["denim blue"], alpha=1, zorder=3)
|
|
153
|
+
plt.text(0.05, 0.95, text, fontdict={'weight': 'bold'}, color=sns.xkcd_rgb["denim blue"],
|
|
154
|
+
ha='left', va='top', transform=ax.transAxes)
|
|
155
|
+
|
|
156
|
+
if diagonal:
|
|
157
|
+
ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
|
|
158
|
+
plt.text(0.91, 0.97, r'$\bf 1:1\ Line$', color='k', ha='right', va='top', transform=ax.transAxes)
|
|
159
|
+
|
|
160
|
+
ax.xaxis.set_major_formatter(ScalarFormatter())
|
|
161
|
+
ax.yaxis.set_major_formatter(ScalarFormatter())
|
|
162
|
+
|
|
163
|
+
plt.show()
|
|
164
|
+
|
|
165
|
+
return fig, ax
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
from .ammonium_rich import ammonium_rich
|
|
1
2
|
from .contour import *
|
|
2
3
|
from .corr_matrix import corr_matrix
|
|
3
4
|
from .diurnal_pattern import *
|
|
4
5
|
from .koschmieder import *
|
|
5
|
-
from .metal_heatmap import metal_heatmaps,
|
|
6
|
-
from .regression import *
|
|
7
|
-
from .scatter import *
|
|
8
|
-
from .templates import *
|
|
6
|
+
from .metal_heatmap import metal_heatmaps, process_data_with_two_df
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
from matplotlib.pyplot import Figure, Axes
|
|
3
|
+
from pandas import DataFrame
|
|
4
|
+
|
|
5
|
+
from AeroViz.plot.utils import set_figure, Unit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@set_figure(figsize=(5, 4))
|
|
9
|
+
def ammonium_rich(df: DataFrame,
|
|
10
|
+
**kwargs
|
|
11
|
+
) -> tuple[Figure, Axes]:
|
|
12
|
+
df = df[['NH4+', 'SO42-', 'NO3-', 'PM2.5']].dropna().copy().div([18, 96, 62, 1])
|
|
13
|
+
df['required_ammonium'] = df['NO3-'] + 2 * df['SO42-']
|
|
14
|
+
|
|
15
|
+
fig, ax = plt.subplots()
|
|
16
|
+
|
|
17
|
+
scatter = ax.scatter(df['required_ammonium'].to_numpy(), df['NH4+'].to_numpy(), c=df['PM2.5'].to_numpy(),
|
|
18
|
+
vmin=0, vmax=70, cmap='jet', marker='o', s=10, alpha=1)
|
|
19
|
+
|
|
20
|
+
ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
|
|
21
|
+
plt.text(0.97, 0.97, r'$\bf 1:1\ Line$', color='k', ha='right', va='top', transform=ax.transAxes)
|
|
22
|
+
|
|
23
|
+
ax.set(xlim=(0, 1.2),
|
|
24
|
+
ylim=(0, 1.2),
|
|
25
|
+
xlabel=r'$\bf NO_{3}^{-}\ +\ 2\ \times\ SO_{4}^{2-}\ (mole\ m^{-3})$',
|
|
26
|
+
ylabel=r'$\bf NH_{4}^{+}\ (mole\ m^{-3})$',
|
|
27
|
+
title=kwargs.get('title', ''))
|
|
28
|
+
|
|
29
|
+
color_bar = plt.colorbar(scatter, label=Unit('PM2.5'), extend='both')
|
|
30
|
+
|
|
31
|
+
# fig.savefig(f'Ammonium_rich_{title}')
|
|
32
|
+
plt.show()
|
|
33
|
+
|
|
34
|
+
return fig, ax
|
|
@@ -10,38 +10,38 @@ __all__ = ['contour']
|
|
|
10
10
|
|
|
11
11
|
@set_figure
|
|
12
12
|
def contour(df, ax: Axes | None = None, **kwargs) -> tuple[Figure, Axes]:
|
|
13
|
-
|
|
13
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
npoints = 1000
|
|
16
|
+
xreg = np.linspace(df.PM25.min(), df.PM25.max(), 83)
|
|
17
|
+
yreg = np.linspace(df.gRH.min(), df.gRH.max(), 34)
|
|
18
|
+
X, Y = np.meshgrid(xreg, yreg)
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
d_f = df.copy()
|
|
21
|
+
df['gRH'] = d_f['gRH'].round(2)
|
|
22
|
+
df['PM25'] = d_f['PM25'].round(2)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
def func(data, *params):
|
|
25
|
+
return params[0] * data ** (params[1])
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
initial_guess = [1.0, 1.0]
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
fit_df = df[['PM25', 'gRH', 'Extinction']].dropna()
|
|
30
|
+
popt, pcov = curve_fit(func, xdata=(fit_df['PM25'] * fit_df['gRH']), ydata=fit_df['Extinction'], p0=initial_guess,
|
|
31
|
+
maxfev=2000000, method='trf')
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
x, y = df.PM25, df.gRH
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
# pcolor = ax.pcolormesh(X, Y, (X * 4.5 * Y ** (1 / 3)), cmap='jet', shading='auto', vmin=0, vmax=843, alpha=0.8)
|
|
36
|
+
Z = func(X * Y, *popt)
|
|
37
|
+
cont = ax.contour(X, Y, Z, colors='black', levels=5, vmin=0, vmax=Z.max())
|
|
38
|
+
conf = ax.contourf(X, Y, Z, cmap='YlGnBu', levels=100, vmin=0, vmax=Z.max())
|
|
39
|
+
ax.clabel(cont, colors=['black'], fmt=lambda s: f"{s:.0f} 1/Mm")
|
|
40
|
+
ax.set(xlabel=Unit('PM25'), ylabel=Unit('gRH'), xlim=(x.min(), x.max()), ylim=(y.min(), y.max()))
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
color_bar = plt.colorbar(conf, pad=0.02, fraction=0.05, label='Extinction (1/Mm)')
|
|
43
|
+
color_bar.ax.set_xticklabels(color_bar.ax.get_xticks().astype(int))
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
plt.show()
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
return fig, ax
|