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.
Files changed (180) hide show
  1. AeroViz/__init__.py +13 -0
  2. AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
  3. AeroViz/data/DEFAULT_DATA.csv +1417 -0
  4. AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
  5. AeroViz/data/hysplit_example_data.txt +101 -0
  6. AeroViz/dataProcess/Chemistry/__init__.py +149 -0
  7. AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
  8. AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
  9. AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
  10. AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
  11. AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
  12. AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
  13. AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
  14. AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
  15. AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
  16. AeroViz/dataProcess/Optical/__init__.py +281 -0
  17. AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
  18. AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
  19. AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
  20. AeroViz/dataProcess/Optical/_derived.py +518 -0
  21. AeroViz/dataProcess/Optical/_extinction.py +123 -0
  22. AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
  23. AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
  24. AeroViz/dataProcess/Optical/coefficient.py +72 -0
  25. AeroViz/dataProcess/Optical/fRH.pkl +0 -0
  26. AeroViz/dataProcess/Optical/mie_theory.py +260 -0
  27. AeroViz/dataProcess/README.md +271 -0
  28. AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
  29. AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
  30. AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
  31. AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
  32. AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
  33. AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
  34. AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
  35. AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
  36. AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
  37. AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
  38. AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
  39. AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
  40. AeroViz/dataProcess/SizeDistr/prop.py +62 -0
  41. AeroViz/dataProcess/VOC/__init__.py +14 -0
  42. AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
  43. AeroViz/dataProcess/VOC/_potential_par.py +108 -0
  44. AeroViz/dataProcess/VOC/support_voc.json +446 -0
  45. AeroViz/dataProcess/__init__.py +66 -0
  46. AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
  47. AeroViz/dataProcess/core/__init__.py +272 -0
  48. AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
  49. AeroViz/mcp_server.py +352 -0
  50. AeroViz/plot/__init__.py +13 -0
  51. AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
  52. AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
  53. AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
  54. AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
  55. AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
  56. AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
  57. AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
  58. AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
  59. AeroViz/plot/bar.py +126 -0
  60. AeroViz/plot/box.py +69 -0
  61. AeroViz/plot/distribution/__init__.py +1 -0
  62. AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
  63. AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
  64. AeroViz/plot/distribution/distribution.py +576 -0
  65. AeroViz/plot/meteorology/CBPF.py +295 -0
  66. AeroViz/plot/meteorology/__init__.py +3 -0
  67. AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
  68. AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
  69. AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
  70. AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
  71. AeroViz/plot/meteorology/hysplit.py +93 -0
  72. AeroViz/plot/meteorology/wind_rose.py +77 -0
  73. AeroViz/plot/optical/__init__.py +1 -0
  74. AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
  75. AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
  76. AeroViz/plot/optical/optical.py +388 -0
  77. AeroViz/plot/pie.py +210 -0
  78. AeroViz/plot/radar.py +184 -0
  79. AeroViz/plot/regression.py +200 -0
  80. AeroViz/plot/scatter.py +174 -0
  81. AeroViz/plot/templates/__init__.py +6 -0
  82. AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  83. AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
  84. AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
  85. AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
  86. AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
  87. AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
  88. AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
  89. AeroViz/plot/templates/ammonium_rich.py +34 -0
  90. AeroViz/plot/templates/contour.py +47 -0
  91. AeroViz/plot/templates/corr_matrix.py +267 -0
  92. AeroViz/plot/templates/diurnal_pattern.py +61 -0
  93. AeroViz/plot/templates/koschmieder.py +95 -0
  94. AeroViz/plot/templates/metal_heatmap.py +164 -0
  95. AeroViz/plot/timeseries/__init__.py +2 -0
  96. AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
  97. AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
  98. AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
  99. AeroViz/plot/timeseries/template.py +47 -0
  100. AeroViz/plot/timeseries/timeseries.py +446 -0
  101. AeroViz/plot/utils/__init__.py +4 -0
  102. AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  103. AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
  104. AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
  105. AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
  106. AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
  107. AeroViz/plot/utils/_color.py +71 -0
  108. AeroViz/plot/utils/_unit.py +55 -0
  109. AeroViz/plot/utils/fRH.json +390 -0
  110. AeroViz/plot/utils/plt_utils.py +92 -0
  111. AeroViz/plot/utils/sklearn_utils.py +49 -0
  112. AeroViz/plot/utils/units.json +89 -0
  113. AeroViz/plot/violin.py +80 -0
  114. AeroViz/rawDataReader/FLOW.md +138 -0
  115. AeroViz/rawDataReader/__init__.py +220 -0
  116. AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
  117. AeroViz/rawDataReader/config/__init__.py +0 -0
  118. AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
  119. AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
  120. AeroViz/rawDataReader/config/supported_instruments.py +135 -0
  121. AeroViz/rawDataReader/core/__init__.py +658 -0
  122. AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
  123. AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
  124. AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
  125. AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
  126. AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
  127. AeroViz/rawDataReader/core/logger.py +171 -0
  128. AeroViz/rawDataReader/core/pre_process.py +308 -0
  129. AeroViz/rawDataReader/core/qc.py +961 -0
  130. AeroViz/rawDataReader/core/report.py +579 -0
  131. AeroViz/rawDataReader/script/AE33.py +173 -0
  132. AeroViz/rawDataReader/script/AE43.py +151 -0
  133. AeroViz/rawDataReader/script/APS.py +339 -0
  134. AeroViz/rawDataReader/script/Aurora.py +191 -0
  135. AeroViz/rawDataReader/script/BAM1020.py +90 -0
  136. AeroViz/rawDataReader/script/BC1054.py +161 -0
  137. AeroViz/rawDataReader/script/EPA.py +79 -0
  138. AeroViz/rawDataReader/script/GRIMM.py +68 -0
  139. AeroViz/rawDataReader/script/IGAC.py +140 -0
  140. AeroViz/rawDataReader/script/MA350.py +179 -0
  141. AeroViz/rawDataReader/script/Minion.py +218 -0
  142. AeroViz/rawDataReader/script/NEPH.py +199 -0
  143. AeroViz/rawDataReader/script/OCEC.py +173 -0
  144. AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
  145. AeroViz/rawDataReader/script/SMPS.py +389 -0
  146. AeroViz/rawDataReader/script/TEOM.py +181 -0
  147. AeroViz/rawDataReader/script/VOC.py +106 -0
  148. AeroViz/rawDataReader/script/Xact.py +244 -0
  149. AeroViz/rawDataReader/script/__init__.py +28 -0
  150. AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
  151. AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
  152. AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
  153. AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
  154. AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
  155. AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
  156. AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
  157. AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
  158. AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
  159. AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
  160. AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
  161. AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
  162. AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
  163. AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
  164. AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
  165. AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
  166. AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
  167. AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
  168. AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
  169. AeroViz/tools/__init__.py +2 -0
  170. AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  171. AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
  172. AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
  173. AeroViz/tools/database.py +95 -0
  174. AeroViz/tools/dataclassifier.py +117 -0
  175. AeroViz/tools/dataprinter.py +58 -0
  176. aeroviz-0.1.21.dist-info/METADATA +294 -0
  177. aeroviz-0.1.21.dist-info/RECORD +180 -0
  178. aeroviz-0.1.21.dist-info/WHEEL +5 -0
  179. aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
  180. 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
@@ -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
@@ -0,0 +1,6 @@
1
+ from .ammonium_rich import ammonium_rich
2
+ from .contour import *
3
+ from .corr_matrix import corr_matrix, cross_corr_matrix
4
+ from .diurnal_pattern import *
5
+ from .koschmieder 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
@@ -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