AeroViz 0.1.21__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.
- AeroViz/__init__.py +13 -0
- AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/data/DEFAULT_DATA.csv +1417 -0
- AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
- AeroViz/data/hysplit_example_data.txt +101 -0
- AeroViz/dataProcess/Chemistry/__init__.py +149 -0
- AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
- AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
- AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
- AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
- AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
- AeroViz/dataProcess/Optical/__init__.py +281 -0
- AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/_derived.py +518 -0
- AeroViz/dataProcess/Optical/_extinction.py +123 -0
- AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
- AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
- AeroViz/dataProcess/Optical/coefficient.py +72 -0
- AeroViz/dataProcess/Optical/fRH.pkl +0 -0
- AeroViz/dataProcess/Optical/mie_theory.py +260 -0
- AeroViz/dataProcess/README.md +271 -0
- AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
- AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
- AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
- AeroViz/dataProcess/SizeDistr/prop.py +62 -0
- AeroViz/dataProcess/VOC/__init__.py +14 -0
- AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/VOC/_potential_par.py +108 -0
- AeroViz/dataProcess/VOC/support_voc.json +446 -0
- AeroViz/dataProcess/__init__.py +66 -0
- AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/core/__init__.py +272 -0
- AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/mcp_server.py +352 -0
- AeroViz/plot/__init__.py +13 -0
- AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +69 -0
- AeroViz/plot/distribution/__init__.py +1 -0
- AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/distribution.py +576 -0
- AeroViz/plot/meteorology/CBPF.py +295 -0
- AeroViz/plot/meteorology/__init__.py +3 -0
- AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/hysplit.py +93 -0
- AeroViz/plot/meteorology/wind_rose.py +77 -0
- AeroViz/plot/optical/__init__.py +1 -0
- AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
- AeroViz/plot/optical/optical.py +388 -0
- AeroViz/plot/pie.py +210 -0
- AeroViz/plot/radar.py +184 -0
- AeroViz/plot/regression.py +200 -0
- AeroViz/plot/scatter.py +174 -0
- AeroViz/plot/templates/__init__.py +6 -0
- AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/contour.py +47 -0
- AeroViz/plot/templates/corr_matrix.py +267 -0
- AeroViz/plot/templates/diurnal_pattern.py +61 -0
- AeroViz/plot/templates/koschmieder.py +95 -0
- AeroViz/plot/templates/metal_heatmap.py +164 -0
- AeroViz/plot/timeseries/__init__.py +2 -0
- AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +446 -0
- AeroViz/plot/utils/__init__.py +4 -0
- AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/_color.py +71 -0
- AeroViz/plot/utils/_unit.py +55 -0
- AeroViz/plot/utils/fRH.json +390 -0
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +89 -0
- AeroViz/plot/violin.py +80 -0
- AeroViz/rawDataReader/FLOW.md +138 -0
- AeroViz/rawDataReader/__init__.py +220 -0
- AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__init__.py +0 -0
- AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/supported_instruments.py +135 -0
- AeroViz/rawDataReader/core/__init__.py +658 -0
- AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/logger.py +171 -0
- AeroViz/rawDataReader/core/pre_process.py +308 -0
- AeroViz/rawDataReader/core/qc.py +961 -0
- AeroViz/rawDataReader/core/report.py +579 -0
- AeroViz/rawDataReader/script/AE33.py +173 -0
- AeroViz/rawDataReader/script/AE43.py +151 -0
- AeroViz/rawDataReader/script/APS.py +339 -0
- AeroViz/rawDataReader/script/Aurora.py +191 -0
- AeroViz/rawDataReader/script/BAM1020.py +90 -0
- AeroViz/rawDataReader/script/BC1054.py +161 -0
- AeroViz/rawDataReader/script/EPA.py +79 -0
- AeroViz/rawDataReader/script/GRIMM.py +68 -0
- AeroViz/rawDataReader/script/IGAC.py +140 -0
- AeroViz/rawDataReader/script/MA350.py +179 -0
- AeroViz/rawDataReader/script/Minion.py +218 -0
- AeroViz/rawDataReader/script/NEPH.py +199 -0
- AeroViz/rawDataReader/script/OCEC.py +173 -0
- AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
- AeroViz/rawDataReader/script/SMPS.py +389 -0
- AeroViz/rawDataReader/script/TEOM.py +181 -0
- AeroViz/rawDataReader/script/VOC.py +106 -0
- AeroViz/rawDataReader/script/Xact.py +244 -0
- AeroViz/rawDataReader/script/__init__.py +28 -0
- AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__init__.py +2 -0
- AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
- AeroViz/tools/database.py +95 -0
- AeroViz/tools/dataclassifier.py +117 -0
- AeroViz/tools/dataprinter.py +58 -0
- aeroviz-0.1.21.dist-info/METADATA +294 -0
- aeroviz-0.1.21.dist-info/RECORD +180 -0
- aeroviz-0.1.21.dist-info/WHEEL +5 -0
- aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
- aeroviz-0.1.21.dist-info/top_level.txt +1 -0
AeroViz/plot/radar.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
from matplotlib.patches import Circle, RegularPolygon
|
|
4
|
+
from matplotlib.path import Path
|
|
5
|
+
from matplotlib.projections import register_projection
|
|
6
|
+
from matplotlib.projections.polar import PolarAxes
|
|
7
|
+
from matplotlib.spines import Spine
|
|
8
|
+
from matplotlib.transforms import Affine2D
|
|
9
|
+
|
|
10
|
+
from AeroViz.plot.utils import *
|
|
11
|
+
|
|
12
|
+
__all__ = ['radar']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def radar_factory(num_vars, frame='circle'):
|
|
16
|
+
"""
|
|
17
|
+
Create a radar chart with `num_vars` axes.
|
|
18
|
+
|
|
19
|
+
This function creates a RadarAxes projection and registers it.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
num_vars : int
|
|
24
|
+
Number of variables for radar chart.
|
|
25
|
+
frame : {'circle', 'polygon'}
|
|
26
|
+
Shape of frame surrounding axes.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
# calculate evenly-spaced axis angles
|
|
30
|
+
theta = np.linspace(0, 2 * np.pi, num_vars, endpoint=False)
|
|
31
|
+
|
|
32
|
+
class RadarTransform(PolarAxes.PolarTransform):
|
|
33
|
+
|
|
34
|
+
def transform_path_non_affine(self, path):
|
|
35
|
+
# Paths with non-unit interpolation steps correspond to gridlines,
|
|
36
|
+
# in which case we force interpolation (to defeat PolarTransform's
|
|
37
|
+
# autoconversion to circular arcs).
|
|
38
|
+
if path._interpolation_steps > 1:
|
|
39
|
+
path = path.interpolated(num_vars)
|
|
40
|
+
return Path(self.transform(path.vertices), path.codes)
|
|
41
|
+
|
|
42
|
+
class RadarAxes(PolarAxes):
|
|
43
|
+
|
|
44
|
+
name = 'radar'
|
|
45
|
+
PolarTransform = RadarTransform
|
|
46
|
+
|
|
47
|
+
def __init__(self, *args, **kwargs):
|
|
48
|
+
super().__init__(*args, **kwargs)
|
|
49
|
+
# rotate plot such that the first axis is at the top
|
|
50
|
+
self.set_theta_zero_location('N')
|
|
51
|
+
|
|
52
|
+
def fill(self, *args, closed=True, **kwargs):
|
|
53
|
+
"""Override fill so that line is closed by default"""
|
|
54
|
+
return super().fill(closed=closed, *args, **kwargs)
|
|
55
|
+
|
|
56
|
+
def plot(self, *args, **kwargs):
|
|
57
|
+
"""Override plot so that line is closed by default"""
|
|
58
|
+
lines = super().plot(*args, **kwargs)
|
|
59
|
+
for line in lines:
|
|
60
|
+
self._close_line(line)
|
|
61
|
+
|
|
62
|
+
def _close_line(self, line):
|
|
63
|
+
x, y = line.get_data()
|
|
64
|
+
if x[0] != x[-1]:
|
|
65
|
+
x = np.append(x, x[0])
|
|
66
|
+
y = np.append(y, y[0])
|
|
67
|
+
line.set_data(x, y)
|
|
68
|
+
|
|
69
|
+
def set_varlabels(self, labels):
|
|
70
|
+
self.set_thetagrids(np.degrees(theta), labels)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def _gen_axes_patch():
|
|
74
|
+
# The Axes patch must be centered at (0.5, 0.5) and of radius 0.5
|
|
75
|
+
# in axes coordinates.
|
|
76
|
+
if frame == 'circle':
|
|
77
|
+
return Circle((0.5, 0.5), 0.5)
|
|
78
|
+
elif frame == 'polygon':
|
|
79
|
+
return RegularPolygon((0.5, 0.5), num_vars,
|
|
80
|
+
radius=.5, edgecolor="k")
|
|
81
|
+
else:
|
|
82
|
+
raise ValueError("Unknown value for 'frame': %s" % frame)
|
|
83
|
+
|
|
84
|
+
def _gen_axes_spines(self):
|
|
85
|
+
if frame == 'circle':
|
|
86
|
+
return super()._gen_axes_spines()
|
|
87
|
+
elif frame == 'polygon':
|
|
88
|
+
# spine_type must be 'left'/'right'/'top'/'bottom'/'circle'.
|
|
89
|
+
spine = Spine(axes=self,
|
|
90
|
+
spine_type='circle',
|
|
91
|
+
path=Path.unit_regular_polygon(num_vars))
|
|
92
|
+
# unit_regular_polygon gives a polygon of radius 1 centered at
|
|
93
|
+
# (0, 0) but we want a polygon of radius 0.5 centered at (0.5,
|
|
94
|
+
# 0.5) in axes coordinates.
|
|
95
|
+
spine.set_transform(Affine2D().scale(.5).translate(.5, .5)
|
|
96
|
+
+ self.transAxes)
|
|
97
|
+
return {'polar': spine}
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError("Unknown value for 'frame': %s" % frame)
|
|
100
|
+
|
|
101
|
+
register_projection(RadarAxes)
|
|
102
|
+
return theta
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@set_figure(figsize=(3, 3))
|
|
106
|
+
def radar(data, labels=None, legend_labels=None, **kwargs) -> tuple[plt.Figure, plt.Axes]:
|
|
107
|
+
"""
|
|
108
|
+
Creates a radar chart based on the provided data.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
data : list of list
|
|
113
|
+
A 2D list where each inner list represents a factor, and each element
|
|
114
|
+
within the inner lists represents a value for a species.
|
|
115
|
+
Shape: (n_factors, n_species)
|
|
116
|
+
Example: [[0.88, 0.01, 0.03, ...], [0.07, 0.95, 0.04, ...], ...]
|
|
117
|
+
|
|
118
|
+
labels : list, optional
|
|
119
|
+
A list of strings representing the names of species (variables).
|
|
120
|
+
If provided, it should have the same length as the number of elements
|
|
121
|
+
in each inner list of `data`.
|
|
122
|
+
Example: ['Sulfate', 'Nitrate', 'EC', 'OC1', 'OC2', 'OP', 'CO', 'O3']
|
|
123
|
+
|
|
124
|
+
legend_labels : list, optional
|
|
125
|
+
A list of strings for labeling each factor in the legend.
|
|
126
|
+
If provided, it should have the same length as the number of inner lists in `data`.
|
|
127
|
+
|
|
128
|
+
**kwargs : dict
|
|
129
|
+
Additional keyword arguments to be passed to the plotting function.
|
|
130
|
+
This may include 'title' for setting the chart title.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
tuple[plt.Figure, plt.Axes]
|
|
135
|
+
A tuple containing the Figure and Axes objects of the created plot.
|
|
136
|
+
|
|
137
|
+
Example
|
|
138
|
+
-------
|
|
139
|
+
>>> data = [[0.88, 0.01, 0.03, 0.03, 0.00, 0.06, 0.01, 0.00],
|
|
140
|
+
>>> [0.07, 0.95, 0.04, 0.05, 0.00, 0.02, 0.01, 0.00],
|
|
141
|
+
>>> [0.01, 0.02, 0.85, 0.19, 0.05, 0.10, 0.00, 0.00],
|
|
142
|
+
>>> [0.02, 0.01, 0.07, 0.01, 0.21, 0.12, 0.98, 0.00],
|
|
143
|
+
>>> [0.01, 0.01, 0.02, 0.71, 0.74, 0.70, 0.30, 0.20]]
|
|
144
|
+
>>> labels = ['Sulfate', 'Nitrate', 'EC', 'OC1', 'OC2', 'OP', 'CO', 'O3']
|
|
145
|
+
>>> fig, ax = radar(data, labels=labels, title='Basecase')
|
|
146
|
+
|
|
147
|
+
Note
|
|
148
|
+
----
|
|
149
|
+
The first dimension of `data` represents each factor, while the second
|
|
150
|
+
dimension represents each species.
|
|
151
|
+
"""
|
|
152
|
+
theta = radar_factory(np.array(data).shape[1], frame='polygon')
|
|
153
|
+
|
|
154
|
+
fig, ax = plt.subplots(subplot_kw=dict(projection='radar'))
|
|
155
|
+
fig.subplots_adjust(wspace=0.25, hspace=0.20, top=0.80, bottom=0.05, right=0.80)
|
|
156
|
+
|
|
157
|
+
colors = ['b', 'r', 'g', 'm', 'y']
|
|
158
|
+
|
|
159
|
+
# Plot the four cases from the example data on separate axes
|
|
160
|
+
for d, color in zip(data, colors):
|
|
161
|
+
ax.plot(theta, d, color=color)
|
|
162
|
+
ax.fill(theta, d, facecolor=color, alpha=0.25, label='_nolegend_')
|
|
163
|
+
|
|
164
|
+
ax.set_varlabels(labels)
|
|
165
|
+
ax.set_rgrids([0.2, 0.4, 0.6, 0.8])
|
|
166
|
+
ax.set(title=kwargs.get('title', ''))
|
|
167
|
+
|
|
168
|
+
# add legend relative to top-left plot
|
|
169
|
+
legend_labels = legend_labels or ('Factor 1', 'Factor 2', 'Factor 3', 'Factor 4', 'Factor 5')
|
|
170
|
+
legend = ax.legend(legend_labels, loc=(0.95, 0.95), labelspacing=0.1)
|
|
171
|
+
|
|
172
|
+
plt.show()
|
|
173
|
+
|
|
174
|
+
return fig, ax
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == '__main__':
|
|
178
|
+
data = [[0.88, 0.01, 0.03, 0.03, 0.00, 0.06, 0.01, 0.00],
|
|
179
|
+
[0.07, 0.95, 0.04, 0.05, 0.00, 0.02, 0.01, 0.00],
|
|
180
|
+
[0.01, 0.02, 0.85, 0.19, 0.05, 0.10, 0.00, 0.00],
|
|
181
|
+
[0.02, 0.01, 0.07, 0.01, 0.21, 0.12, 0.98, 0.00],
|
|
182
|
+
[0.01, 0.01, 0.02, 0.71, 0.74, 0.70, 0.30, 0.20]]
|
|
183
|
+
|
|
184
|
+
fig, ax = radar(data=data, labels=['Sulfate', 'Nitrate', 'EC', 'OC1', 'OC2', 'OP', 'CO', 'O3'], title='Basecase')
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
Examples
|
|
63
|
+
--------
|
|
64
|
+
>>> from AeroViz import plot
|
|
65
|
+
>>>
|
|
66
|
+
>>> plot.linear_regression(df, x='X', y=['Y1', 'Y2'], labels=['Label1', 'Label2'],
|
|
67
|
+
... diagonal=True, xlim=(0, 10), ylim=(0, 20),
|
|
68
|
+
... xlabel="X-axis", ylabel="Y-axis", title="Scatter Plot with Regressions")
|
|
69
|
+
"""
|
|
70
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
71
|
+
|
|
72
|
+
if not isinstance(x, str):
|
|
73
|
+
x = x[0]
|
|
74
|
+
|
|
75
|
+
if not isinstance(y, list):
|
|
76
|
+
y = [y]
|
|
77
|
+
|
|
78
|
+
if labels is None:
|
|
79
|
+
labels = y
|
|
80
|
+
|
|
81
|
+
df = df.dropna(subset=[x, *y])
|
|
82
|
+
x_array = df[[x]].to_numpy()
|
|
83
|
+
|
|
84
|
+
color_cycle = Color.linecolor
|
|
85
|
+
|
|
86
|
+
handles, text_list = [], []
|
|
87
|
+
|
|
88
|
+
for i, y_var in enumerate(y):
|
|
89
|
+
y_array = df[[y_var]].to_numpy()
|
|
90
|
+
|
|
91
|
+
color = color_cycle[i % len(color_cycle)]
|
|
92
|
+
|
|
93
|
+
scatter = ax.scatter(x_array, y_array, s=25, color=color['face'], edgecolors=color['edge'], alpha=0.8,
|
|
94
|
+
label=labels[i])
|
|
95
|
+
handles.append(scatter)
|
|
96
|
+
|
|
97
|
+
text, y_predict, slope = linear_regression_base(x_array, y_array,
|
|
98
|
+
columns=labels[i],
|
|
99
|
+
positive=positive,
|
|
100
|
+
fit_intercept=fit_intercept)
|
|
101
|
+
|
|
102
|
+
text_list.append(f'{labels[i]}:\n{text}')
|
|
103
|
+
plt.plot(x_array, y_predict, linewidth=3, color=color['line'], alpha=1, zorder=3)
|
|
104
|
+
|
|
105
|
+
ax.set(xlim=kwargs.get('xlim'), ylim=kwargs.get('ylim'), xlabel=Unit(x), ylabel=Unit(y[0]),
|
|
106
|
+
title=kwargs.get('title'))
|
|
107
|
+
|
|
108
|
+
# Add regression info to the legend
|
|
109
|
+
leg = plt.legend(handles=handles, labels=text_list, loc='upper left', prop={'weight': 'bold'})
|
|
110
|
+
|
|
111
|
+
for text, color in zip(leg.get_texts(), [color['line'] for color in color_cycle]):
|
|
112
|
+
text.set_color(color)
|
|
113
|
+
|
|
114
|
+
if diagonal:
|
|
115
|
+
ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
|
|
116
|
+
plt.text(0.97, 0.97, r'$\bf 1:1\ Line$', color='k', ha='right', va='top', transform=ax.transAxes)
|
|
117
|
+
|
|
118
|
+
plt.show()
|
|
119
|
+
|
|
120
|
+
return fig, ax
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@set_figure
|
|
124
|
+
def multiple_linear_regression(df: pd.DataFrame,
|
|
125
|
+
x: str | list[str],
|
|
126
|
+
y: str | list[str],
|
|
127
|
+
labels: str | list[str] = None,
|
|
128
|
+
ax: Axes | None = None,
|
|
129
|
+
diagonal=False,
|
|
130
|
+
positive: bool = True,
|
|
131
|
+
fit_intercept: bool = True,
|
|
132
|
+
**kwargs
|
|
133
|
+
) -> tuple[Figure, Axes]:
|
|
134
|
+
"""
|
|
135
|
+
Perform multiple linear regression analysis and plot the results.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
df : pd.DataFrame
|
|
140
|
+
Input DataFrame containing the data.
|
|
141
|
+
x : str or list of str
|
|
142
|
+
Column name(s) for the independent variable(s). Can be a single string or a list of strings.
|
|
143
|
+
y : str or list of str
|
|
144
|
+
Column name(s) for the dependent variable(s). Can be a single string or a list of strings.
|
|
145
|
+
labels : str or list of str, optional
|
|
146
|
+
Labels for the dependent variable(s). If None, column names are used as labels. Default is None.
|
|
147
|
+
ax : Axes, optional
|
|
148
|
+
Matplotlib Axes object to use for the plot. If None, a new subplot is created. Default is None.
|
|
149
|
+
diagonal : bool, optional
|
|
150
|
+
Whether to include a diagonal line (1:1 line) in the plot. Default is False.
|
|
151
|
+
positive : bool, optional
|
|
152
|
+
Whether to constrain the regression coefficients to be positive. Default is True.
|
|
153
|
+
fit_intercept: bool, optional
|
|
154
|
+
Whether to calculate the intercept for this model. Default is True.
|
|
155
|
+
**kwargs
|
|
156
|
+
Additional keyword arguments for plot customization.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
tuple[Figure, Axes]
|
|
161
|
+
The Figure and Axes containing the regression plot.
|
|
162
|
+
|
|
163
|
+
Notes
|
|
164
|
+
-----
|
|
165
|
+
This function performs multiple linear regression analysis using the input DataFrame.
|
|
166
|
+
It supports multiple independent variables and can plot the regression results.
|
|
167
|
+
|
|
168
|
+
Examples
|
|
169
|
+
--------
|
|
170
|
+
>>> from AeroViz import plot
|
|
171
|
+
>>>
|
|
172
|
+
>>> plot.multiple_linear_regression(df, x=['X1', 'X2'], y='Y', labels=['Y1', 'Y2'],
|
|
173
|
+
... diagonal=True, fit_intercept=True,
|
|
174
|
+
... xlabel="X-axis", ylabel="Y-axis", title="Multiple Linear Regression Plot")
|
|
175
|
+
"""
|
|
176
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
177
|
+
|
|
178
|
+
if not isinstance(x, list):
|
|
179
|
+
x = [x]
|
|
180
|
+
|
|
181
|
+
if not isinstance(y, str):
|
|
182
|
+
y = y[0]
|
|
183
|
+
|
|
184
|
+
if labels is None:
|
|
185
|
+
labels = x
|
|
186
|
+
|
|
187
|
+
df = df[[*x, y]].dropna()
|
|
188
|
+
x_array = df[[*x]].to_numpy()
|
|
189
|
+
y_array = df[[y]].to_numpy()
|
|
190
|
+
|
|
191
|
+
text, y_predict, coefficients = linear_regression_base(x_array, y_array,
|
|
192
|
+
columns=labels,
|
|
193
|
+
positive=positive,
|
|
194
|
+
fit_intercept=fit_intercept)
|
|
195
|
+
|
|
196
|
+
df = pd.DataFrame(np.concatenate([y_array, y_predict], axis=1), columns=['y_actual', 'y_predict'])
|
|
197
|
+
|
|
198
|
+
linear_regression(df, x='y_actual', y='y_predict', ax=ax, regression=True, diagonal=diagonal)
|
|
199
|
+
|
|
200
|
+
return fig, ax
|
AeroViz/plot/scatter.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
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
|
+
color: str | None = '#7a97c9',
|
|
26
|
+
s: str | None = None,
|
|
27
|
+
cmap='jet',
|
|
28
|
+
regression=False,
|
|
29
|
+
regression_line_color: str | None = sns.xkcd_rgb["denim blue"],
|
|
30
|
+
diagonal=False,
|
|
31
|
+
ax: Axes | None = None,
|
|
32
|
+
**kwargs
|
|
33
|
+
) -> tuple[Figure, Axes]:
|
|
34
|
+
"""
|
|
35
|
+
Creates a scatter plot with optional color and size encoding.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
df : pd.DataFrame
|
|
40
|
+
The DataFrame containing the data to plot.
|
|
41
|
+
x : str
|
|
42
|
+
The column name for the x-axis values.
|
|
43
|
+
y : str
|
|
44
|
+
The column name for the y-axis values.
|
|
45
|
+
c : str, optional
|
|
46
|
+
The column name for c encoding. Default is None.
|
|
47
|
+
color : str, optional
|
|
48
|
+
The column name for color encoding. Default is None.
|
|
49
|
+
s : str, optional
|
|
50
|
+
The column name for size encoding. Default is None.
|
|
51
|
+
cmap : str, optional
|
|
52
|
+
The colormap to use for the color encoding. Default is 'jet'.
|
|
53
|
+
regression : bool, optional
|
|
54
|
+
If True, fits and plots a linear regression line. Default is False.
|
|
55
|
+
regression_line_color : str, optional
|
|
56
|
+
The color of the regression line. Default is 'sns.xkcd_rgb["denim blue"]'.
|
|
57
|
+
diagonal : bool, optional
|
|
58
|
+
If True, plots a 1:1 diagonal line. Default is False.
|
|
59
|
+
ax : Axes, optional
|
|
60
|
+
The matplotlib Axes to plot on. If not provided, a new figure and axes are created.
|
|
61
|
+
**kwargs : Any
|
|
62
|
+
Additional keyword arguments passed to customize the plot, such as `fig_kws` for figure creation and `xlabel`,
|
|
63
|
+
`ylabel`, `xlim`, `ylim`, `title` for axis labeling and limits.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
fig : Figure
|
|
68
|
+
The matplotlib Figure object.
|
|
69
|
+
ax : Axes
|
|
70
|
+
The matplotlib Axes object with the scatter plot.
|
|
71
|
+
|
|
72
|
+
Notes
|
|
73
|
+
-----
|
|
74
|
+
- If both `c` and `s` are provided, the scatter plot will encode data points using both color and size.
|
|
75
|
+
- If only `c` is provided, data points will be color-coded according to the values in the `c` column.
|
|
76
|
+
- If only `s` is provided, data points will be sized according to the values in the `s` column.
|
|
77
|
+
- If neither `c` nor `s` is provided, a basic scatter plot is created.
|
|
78
|
+
- The `regression` option will add a linear regression line and display the equation on the plot.
|
|
79
|
+
- The `diagonal` option will add a 1:1 reference line to the plot.
|
|
80
|
+
|
|
81
|
+
Examples
|
|
82
|
+
--------
|
|
83
|
+
>>> import pandas as pd
|
|
84
|
+
>>> from AeroViz.plot import scatter
|
|
85
|
+
>>> df = pd.DataFrame({
|
|
86
|
+
>>> 'x': [1, 2, 3, 4],
|
|
87
|
+
>>> 'y': [1.1, 2.0, 2.9, 4.1],
|
|
88
|
+
>>> 'color': [10, 20, 30, 40],
|
|
89
|
+
>>> 'size': [100, 200, 300, 400]
|
|
90
|
+
>>> })
|
|
91
|
+
>>> fig, ax = scatter(df, x='x', y='y', c='color', s='size', regression=True, diagonal=True)
|
|
92
|
+
"""
|
|
93
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
94
|
+
|
|
95
|
+
if c is not None and s is not None:
|
|
96
|
+
df_ = df.dropna(subset=[x, y, c, s]).copy()
|
|
97
|
+
x_data, y_data, c_data, s_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[c].to_numpy(), df_[s].to_numpy()
|
|
98
|
+
check_empty(x_data, y_data, c_data, s_data)
|
|
99
|
+
|
|
100
|
+
scatter = ax.scatter(x_data, y_data, c=c_data,
|
|
101
|
+
norm=Normalize(vmin=np.percentile(c_data, 10), vmax=np.percentile(c_data, 90)),
|
|
102
|
+
cmap=cmap, s=50 * (s_data / s_data.max()) ** 1.5, alpha=0.7, edgecolors=None)
|
|
103
|
+
colorbar = True
|
|
104
|
+
|
|
105
|
+
dot = np.linspace(s_data.min(), s_data.max(), 6).round(-1)
|
|
106
|
+
|
|
107
|
+
for dott in dot[1:-1]:
|
|
108
|
+
plt.scatter([], [], c='k', alpha=0.8, s=50 * (dott / s_data.max()) ** 1.5, label='{:.0f}'.format(dott))
|
|
109
|
+
|
|
110
|
+
plt.legend(title=Unit(s))
|
|
111
|
+
|
|
112
|
+
elif c is not None:
|
|
113
|
+
df_ = df.dropna(subset=[x, y, c]).copy()
|
|
114
|
+
x_data, y_data, c_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[c].to_numpy()
|
|
115
|
+
check_empty(x_data, y_data, c_data)
|
|
116
|
+
|
|
117
|
+
scatter = ax.scatter(x_data, y_data, c=c_data, vmin=c_data.min(), vmax=np.percentile(c_data, 90), cmap=cmap,
|
|
118
|
+
alpha=0.7,
|
|
119
|
+
edgecolors=None)
|
|
120
|
+
colorbar = True
|
|
121
|
+
|
|
122
|
+
elif s is not None:
|
|
123
|
+
df_ = df.dropna(subset=[x, y, s]).copy()
|
|
124
|
+
x_data, y_data, s_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[s].to_numpy()
|
|
125
|
+
check_empty(x_data, y_data, s_data)
|
|
126
|
+
|
|
127
|
+
scatter = ax.scatter(x_data, y_data, s=50 * (s_data / s_data.max()) ** 1.5, color=color, alpha=0.5,
|
|
128
|
+
edgecolors='white')
|
|
129
|
+
colorbar = False
|
|
130
|
+
|
|
131
|
+
# dealing
|
|
132
|
+
dot = np.linspace(s_data.min(), s_data.max(), 6).round(-1)
|
|
133
|
+
|
|
134
|
+
for dott in dot[1:-1]:
|
|
135
|
+
plt.scatter([], [], c='k', alpha=0.8, s=50 * (dott / s_data.max()) ** 1.5, label='{:.0f}'.format(dott))
|
|
136
|
+
|
|
137
|
+
plt.legend(title=Unit(s))
|
|
138
|
+
|
|
139
|
+
else:
|
|
140
|
+
df_ = df.dropna(subset=[x, y]).copy()
|
|
141
|
+
x_data, y_data = df_[x].to_numpy(), df_[y].to_numpy()
|
|
142
|
+
check_empty(x_data, y_data)
|
|
143
|
+
|
|
144
|
+
scatter = ax.scatter(x_data, y_data, s=30, color=color, alpha=0.5, edgecolors='white')
|
|
145
|
+
colorbar = False
|
|
146
|
+
|
|
147
|
+
ax.set(xlim=kwargs.get('xlim', (x_data.min(), x_data.max())),
|
|
148
|
+
ylim=kwargs.get('ylim', (y_data.min(), y_data.max())),
|
|
149
|
+
xlabel=kwargs.get('xlabel', Unit(x)),
|
|
150
|
+
ylabel=kwargs.get('ylabel', Unit(y)),
|
|
151
|
+
title=kwargs.get('title', ''))
|
|
152
|
+
|
|
153
|
+
ax.xaxis.set_major_formatter(ScalarFormatter())
|
|
154
|
+
ax.yaxis.set_major_formatter(ScalarFormatter())
|
|
155
|
+
|
|
156
|
+
if colorbar:
|
|
157
|
+
plt.colorbar(scatter, extend='both', label=Unit(c))
|
|
158
|
+
|
|
159
|
+
if regression:
|
|
160
|
+
text, y_predict, slope = linear_regression_base(x_data, y_data)
|
|
161
|
+
ax.plot(x_data, y_predict, linewidth=3, color=regression_line_color, alpha=1, zorder=3)
|
|
162
|
+
plt.text(0.05, 0.95, text, fontdict={'weight': 'bold'}, color=regression_line_color,
|
|
163
|
+
ha='left', va='top', transform=ax.transAxes)
|
|
164
|
+
|
|
165
|
+
if diagonal:
|
|
166
|
+
ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
|
|
167
|
+
|
|
168
|
+
data_range = min(ax.get_xlim()[1] - ax.get_xlim()[0], ax.get_ylim()[1] - ax.get_ylim()[0])
|
|
169
|
+
plt.text(0.9 * data_range, 0.9 * data_range, r'$\bf 1:1\ Line$', color='k', ha='left', va='bottom',
|
|
170
|
+
bbox=dict(facecolor='white', edgecolor='none', alpha=0.1, pad=3))
|
|
171
|
+
|
|
172
|
+
plt.show()
|
|
173
|
+
|
|
174
|
+
return fig, ax
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import numpy as np
|
|
3
|
+
from matplotlib.pyplot import Figure, Axes
|
|
4
|
+
from scipy.optimize import curve_fit
|
|
5
|
+
|
|
6
|
+
from AeroViz.plot.utils import *
|
|
7
|
+
|
|
8
|
+
__all__ = ['contour']
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@set_figure
|
|
12
|
+
def contour(df, ax: Axes | None = None, **kwargs) -> tuple[Figure, Axes]:
|
|
13
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
14
|
+
|
|
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
|
+
|
|
20
|
+
d_f = df.copy()
|
|
21
|
+
df['gRH'] = d_f['gRH'].round(2)
|
|
22
|
+
df['PM25'] = d_f['PM25'].round(2)
|
|
23
|
+
|
|
24
|
+
def func(data, *params):
|
|
25
|
+
return params[0] * data ** (params[1])
|
|
26
|
+
|
|
27
|
+
initial_guess = [1.0, 1.0]
|
|
28
|
+
|
|
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
|
+
|
|
33
|
+
x, y = df.PM25, df.gRH
|
|
34
|
+
|
|
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
|
+
|
|
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
|
+
|
|
45
|
+
plt.show()
|
|
46
|
+
|
|
47
|
+
return fig, ax
|