AeroViz 0.1.6__tar.gz → 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of AeroViz might be problematic. Click here for more details.

Files changed (125) hide show
  1. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/_ocec.py +20 -7
  2. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/__init__.py +1 -0
  3. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/meteorology/meteorology.py +2 -0
  4. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/optical/optical.py +1 -1
  5. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/pie.py +14 -2
  6. aeroviz-0.1.7/AeroViz/plot/radar.py +184 -0
  7. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/scatter.py +16 -7
  8. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/templates/koschmieder.py +11 -8
  9. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/timeseries/timeseries.py +0 -1
  10. aeroviz-0.1.7/AeroViz/rawDataReader/__init__.py +117 -0
  11. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/config/supported_instruments.py +52 -19
  12. aeroviz-0.1.7/AeroViz/rawDataReader/core/__init__.py +317 -0
  13. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/AE33.py +1 -1
  14. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/AE43.py +1 -1
  15. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/Aurora.py +1 -1
  16. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/BC1054.py +1 -1
  17. aeroviz-0.1.7/AeroViz/rawDataReader/script/EPA.py +39 -0
  18. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/GRIMM.py +1 -1
  19. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/IGAC.py +6 -23
  20. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/MA350.py +1 -1
  21. aeroviz-0.1.7/AeroViz/rawDataReader/script/Minion.py +175 -0
  22. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/NEPH.py +1 -1
  23. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/OCEC.py +1 -1
  24. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/SMPS.py +1 -0
  25. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/TEOM.py +2 -2
  26. aeroviz-0.1.7/AeroViz/rawDataReader/script/XRF.py +11 -0
  27. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/__init__.py +2 -2
  28. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz.egg-info/PKG-INFO +46 -24
  29. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz.egg-info/SOURCES.txt +3 -19
  30. {aeroviz-0.1.6 → aeroviz-0.1.7}/PKG-INFO +46 -24
  31. {aeroviz-0.1.6 → aeroviz-0.1.7}/README.md +45 -23
  32. {aeroviz-0.1.6 → aeroviz-0.1.7}/setup.py +1 -1
  33. aeroviz-0.1.6/AeroViz/process/__init__.py +0 -31
  34. aeroviz-0.1.6/AeroViz/process/core/DataProc.py +0 -19
  35. aeroviz-0.1.6/AeroViz/process/core/SizeDist.py +0 -90
  36. aeroviz-0.1.6/AeroViz/process/core/__init__.py +0 -4
  37. aeroviz-0.1.6/AeroViz/process/method/PyMieScatt_update.py +0 -567
  38. aeroviz-0.1.6/AeroViz/process/method/__init__.py +0 -2
  39. aeroviz-0.1.6/AeroViz/process/method/mie_theory.py +0 -260
  40. aeroviz-0.1.6/AeroViz/process/method/prop.py +0 -62
  41. aeroviz-0.1.6/AeroViz/process/script/AbstractDistCalc.py +0 -143
  42. aeroviz-0.1.6/AeroViz/process/script/Chemical.py +0 -177
  43. aeroviz-0.1.6/AeroViz/process/script/IMPACT.py +0 -49
  44. aeroviz-0.1.6/AeroViz/process/script/IMPROVE.py +0 -161
  45. aeroviz-0.1.6/AeroViz/process/script/Others.py +0 -65
  46. aeroviz-0.1.6/AeroViz/process/script/PSD.py +0 -103
  47. aeroviz-0.1.6/AeroViz/process/script/PSD_dry.py +0 -93
  48. aeroviz-0.1.6/AeroViz/process/script/__init__.py +0 -5
  49. aeroviz-0.1.6/AeroViz/process/script/retrieve_RI.py +0 -69
  50. aeroviz-0.1.6/AeroViz/rawDataReader/__init__.py +0 -110
  51. aeroviz-0.1.6/AeroViz/rawDataReader/core/__init__.py +0 -292
  52. aeroviz-0.1.6/AeroViz/rawDataReader/script/EPA_vertical.py +0 -46
  53. aeroviz-0.1.6/AeroViz/rawDataReader/script/Minion.py +0 -103
  54. aeroviz-0.1.6/AeroViz/rawDataReader/script/Table.py +0 -27
  55. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/__init__.py +0 -0
  56. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/data/DEFAULT_DATA.csv +0 -0
  57. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/data/DEFAULT_PNSD_DATA.csv +0 -0
  58. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/__init__.py +0 -0
  59. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/_calculate.py +0 -0
  60. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/_isoropia.py +0 -0
  61. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/_mass_volume.py +0 -0
  62. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/_partition.py +0 -0
  63. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/_teom.py +0 -0
  64. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/isrpia.cnf +0 -0
  65. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
  66. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/Angstrom_exponent.py +0 -0
  67. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/_IMPROVE.py +0 -0
  68. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/__init__.py +0 -0
  69. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/_absorption.py +0 -0
  70. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/_extinction.py +0 -0
  71. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/_mie.py +0 -0
  72. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/_mie_sd.py +0 -0
  73. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/_scattering.py +0 -0
  74. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/Optical/fRH.pkl +0 -0
  75. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/__init__.py +0 -0
  76. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/__merge.py +0 -0
  77. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/_merge.py +0 -0
  78. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/_merge_v1.py +0 -0
  79. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/_merge_v2.py +0 -0
  80. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/_merge_v3.py +0 -0
  81. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/_merge_v4.py +0 -0
  82. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/SizeDistr/_size_distr.py +0 -0
  83. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/VOC/__init__.py +0 -0
  84. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/VOC/_potential_par.py +0 -0
  85. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/VOC/support_voc.json +0 -0
  86. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/__init__.py +0 -0
  87. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/dataProcess/core/__init__.py +0 -0
  88. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/bar.py +0 -0
  89. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/box.py +0 -0
  90. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/distribution/__init__.py +0 -0
  91. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/distribution/distribution.py +0 -0
  92. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/meteorology/__init__.py +0 -0
  93. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/optical/__init__.py +0 -0
  94. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/regression.py +0 -0
  95. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/templates/__init__.py +0 -0
  96. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/templates/ammonium_rich.py +0 -0
  97. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/templates/contour.py +0 -0
  98. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/templates/corr_matrix.py +0 -0
  99. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/templates/diurnal_pattern.py +0 -0
  100. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/templates/metal_heatmap.py +0 -0
  101. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/timeseries/__init__.py +0 -0
  102. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/timeseries/template.py +0 -0
  103. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/utils/__init__.py +0 -0
  104. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/utils/_color.py +0 -0
  105. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/utils/_unit.py +0 -0
  106. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/utils/fRH.json +0 -0
  107. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/utils/plt_utils.py +0 -0
  108. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/utils/sklearn_utils.py +0 -0
  109. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/utils/units.json +0 -0
  110. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/plot/violin.py +0 -0
  111. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/config/__init__.py +0 -0
  112. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/APS_3321.py +0 -0
  113. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/rawDataReader/script/VOC.py +0 -0
  114. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/tools/__init__.py +0 -0
  115. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/tools/database.py +0 -0
  116. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/tools/dataclassifier.py +0 -0
  117. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/tools/dataprinter.py +0 -0
  118. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz/tools/datareader.py +0 -0
  119. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz.egg-info/dependency_links.txt +0 -0
  120. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz.egg-info/requires.txt +0 -0
  121. {aeroviz-0.1.6 → aeroviz-0.1.7}/AeroViz.egg-info/top_level.txt +0 -0
  122. {aeroviz-0.1.6 → aeroviz-0.1.7}/LICENSE +0 -0
  123. {aeroviz-0.1.6 → aeroviz-0.1.7}/MANIFEST.in +0 -0
  124. {aeroviz-0.1.6 → aeroviz-0.1.7}/requirements.txt +0 -0
  125. {aeroviz-0.1.6 → aeroviz-0.1.7}/setup.cfg +0 -0
@@ -1,6 +1,8 @@
1
+ import warnings
2
+
1
3
  import numpy as np
2
4
  from pandas import concat, DataFrame
3
- from scipy.optimize import curve_fit
5
+ from scipy.optimize import curve_fit, least_squares, OptimizeWarning
4
6
 
5
7
  from AeroViz.dataProcess.core import union_index
6
8
 
@@ -22,15 +24,26 @@ def _min_Rsq(_oc, _ec, _rng):
22
24
  for _ocec, _out in _out_table.items():
23
25
  _df = DataFrame([_out.values, _ec.values]).T.dropna()
24
26
 
25
- _x, _y = _df[0], _df[1]
26
- _opt, _ = curve_fit(_func, _x, _y)
27
+ _x, _y = _df[0].values, _df[1].values
28
+
29
+ # 初始參數估計
30
+ slope_guess = (_y[-1] - _y[0]) / (_x[-1] - _x[0])
31
+ intercept_guess = _y[0] - slope_guess * _x[0]
32
+
33
+ try:
34
+ with warnings.catch_warnings():
35
+ warnings.filterwarnings('error')
36
+ _opt, _ = curve_fit(_func, _x, _y, p0=[slope_guess, intercept_guess], maxfev=5000)
37
+ except (RuntimeWarning, OptimizeWarning):
38
+ # 如果 curve_fit 失敗,嘗試使用 least_squares
39
+ residuals = lambda p: _func(_x, *p) - _y
40
+ _opt = least_squares(residuals, [slope_guess, intercept_guess]).x
27
41
 
28
- _tss = np.sum((_y - _y.mean()) ** 2.)
29
- _rss = np.sum((_y - _func(_x, *_opt)) ** 2.)
42
+ _tss = np.sum((_y - np.mean(_y)) ** 2)
43
+ _rss = np.sum((_y - _func(_x, *_opt)) ** 2)
30
44
 
31
- _r2_dic[round(_ocec, 3)] = 1. - _rss / _tss
45
+ _r2_dic[round(_ocec, 3)] = 1 - _rss / _tss
32
46
 
33
- # get the min R2
34
47
  _ratio = DataFrame(_r2_dic, index=[0]).idxmin(axis=1).values[0]
35
48
 
36
49
  return _ratio, _out_table[_ratio]
@@ -4,6 +4,7 @@ from . import optical
4
4
  from .bar import bar
5
5
  from .box import box
6
6
  from .pie import pie, donuts
7
+ from .radar import radar
7
8
  from .regression import linear_regression, multiple_linear_regression
8
9
  from .scatter import scatter
9
10
  from .templates import *
@@ -70,6 +70,7 @@ def wind_rose(df: DataFrame,
70
70
  rlabel_position=rlabel_pos,
71
71
  theta_direction=-1,
72
72
  theta_zero_location='N',
73
+ title=kwargs.get('title', None)
73
74
  )
74
75
  ax.set_thetagrids(angles=[0, 45, 90, 135, 180, 225, 270, 315],
75
76
  labels=["N", "NE", "E", "SE", "S", "SW", "W", "NW"])
@@ -81,6 +82,7 @@ def wind_rose(df: DataFrame,
81
82
  return fig, ax
82
83
 
83
84
 
85
+ # TODO: fix the bug of the CBPF function
84
86
  @set_figure(figsize=(4.3, 4))
85
87
  def CBPF(df: DataFrame,
86
88
  WS: Series | str,
@@ -7,7 +7,7 @@ import numpy as np
7
7
  from matplotlib.pyplot import Figure, Axes
8
8
 
9
9
  from AeroViz.plot.utils import *
10
- from AeroViz.process.method.mie_theory import Mie_Q, Mie_MEE, Mie_PESD
10
+ from temp.process.method.mie_theory import Mie_Q, Mie_MEE, Mie_PESD
11
11
 
12
12
  __all__ = ['Q_plot',
13
13
  'RI_couple',
@@ -81,7 +81,9 @@ def pie(data_set: DataFrame | dict,
81
81
  radius = 4
82
82
  width = 4 if style == 'pie' else 1
83
83
 
84
- text = [''] * pies if style == 'pie' else [Unit(unit) + '\n\n' + '{:.2f}'.format(x) for x in data.sum(axis=1)]
84
+ text = [''] * pies if style == 'pie' else [Unit(unit) + '\n\n' +
85
+ '{:.2f} ± {:.2f}'.format(x, s)
86
+ for x, s in zip(data.sum(axis=1), data.std(axis=1))]
85
87
  pct_distance = 0.6 if style == 'pie' else 0.88
86
88
 
87
89
  fig, ax = plt.subplots(1, pies, figsize=((pies * 2) + 1, 2)) if ax is None else (ax.get_figure(), ax)
@@ -99,7 +101,17 @@ def pie(data_set: DataFrame | dict,
99
101
  pctdistance=1.3, radius=radius, wedgeprops=dict(width=width, edgecolor='w'))
100
102
  ax[i].axis('equal')
101
103
  ax[i].text(0, 0, text[i], ha='center', va='center')
102
- ax[i].set_title(category_names[i])
104
+
105
+ if kwargs.get('title') is None:
106
+ ax[i].set_title(category_names[i])
107
+
108
+ else:
109
+ if len(kwargs.get('title')) == pies:
110
+ title = kwargs.get('title')
111
+ else:
112
+ raise ValueError('The length of the title list must match the number of pies.')
113
+
114
+ ax[i].set_title(title[i])
103
115
 
104
116
  ax[-1].legend(labels, loc='center left', prop={'size': 8, 'weight': 'normal'}, bbox_to_anchor=(1, 0, 1.15, 1))
105
117
 
@@ -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')
@@ -22,9 +22,11 @@ def scatter(df: pd.DataFrame,
22
22
  x: str,
23
23
  y: str,
24
24
  c: str | None = None,
25
+ color: str | None = '#7a97c9',
25
26
  s: str | None = None,
26
27
  cmap='jet',
27
28
  regression=False,
29
+ regression_line_color: str | None = sns.xkcd_rgb["denim blue"],
28
30
  diagonal=False,
29
31
  ax: Axes | None = None,
30
32
  **kwargs
@@ -41,6 +43,8 @@ def scatter(df: pd.DataFrame,
41
43
  y : str
42
44
  The column name for the y-axis values.
43
45
  c : str, optional
46
+ The column name for c encoding. Default is None.
47
+ color : str, optional
44
48
  The column name for color encoding. Default is None.
45
49
  s : str, optional
46
50
  The column name for size encoding. Default is None.
@@ -48,6 +52,8 @@ def scatter(df: pd.DataFrame,
48
52
  The colormap to use for the color encoding. Default is 'jet'.
49
53
  regression : bool, optional
50
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"]'.
51
57
  diagonal : bool, optional
52
58
  If True, plots a 1:1 diagonal line. Default is False.
53
59
  ax : Axes, optional
@@ -118,7 +124,7 @@ def scatter(df: pd.DataFrame,
118
124
  x_data, y_data, s_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[s].to_numpy()
119
125
  check_empty(x_data, y_data, s_data)
120
126
 
121
- scatter = ax.scatter(x_data, y_data, s=50 * (s_data / s_data.max()) ** 1.5, color='#7a97c9', alpha=0.7,
127
+ scatter = ax.scatter(x_data, y_data, s=50 * (s_data / s_data.max()) ** 1.5, color=color, alpha=0.5,
122
128
  edgecolors='white')
123
129
  colorbar = False
124
130
 
@@ -135,7 +141,7 @@ def scatter(df: pd.DataFrame,
135
141
  x_data, y_data = df_[x].to_numpy(), df_[y].to_numpy()
136
142
  check_empty(x_data, y_data)
137
143
 
138
- scatter = ax.scatter(x_data, y_data, s=30, color='#7a97c9', alpha=0.7, edgecolors='white')
144
+ scatter = ax.scatter(x_data, y_data, s=30, color=color, alpha=0.5, edgecolors='white')
139
145
  colorbar = False
140
146
 
141
147
  ax.set(xlim=kwargs.get('xlim', (x_data.min(), x_data.max())),
@@ -144,21 +150,24 @@ def scatter(df: pd.DataFrame,
144
150
  ylabel=kwargs.get('ylabel', Unit(y)),
145
151
  title=kwargs.get('title', ''))
146
152
 
153
+ ax.xaxis.set_major_formatter(ScalarFormatter())
154
+ ax.yaxis.set_major_formatter(ScalarFormatter())
155
+
147
156
  if colorbar:
148
157
  plt.colorbar(scatter, extend='both', label=Unit(c))
149
158
 
150
159
  if regression:
151
160
  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"],
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,
154
163
  ha='left', va='top', transform=ax.transAxes)
155
164
 
156
165
  if diagonal:
157
166
  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
167
 
160
- ax.xaxis.set_major_formatter(ScalarFormatter())
161
- ax.yaxis.set_major_formatter(ScalarFormatter())
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))
162
171
 
163
172
  plt.show()
164
173
 
@@ -10,7 +10,7 @@ from AeroViz.plot.utils import *
10
10
  __all__ = ['koschmieder']
11
11
 
12
12
 
13
- @set_figure
13
+ @set_figure(figsize=(2.4, 3))
14
14
  def koschmieder(df: pd.DataFrame,
15
15
  vis: str,
16
16
  ext: list[str],
@@ -30,8 +30,8 @@ def koschmieder(df: pd.DataFrame,
30
30
 
31
31
  fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
32
32
 
33
- boxcolors = ['#3f83bf', '#a5bf6b']
34
- scattercolor = ['blue', 'green']
33
+ boxcolors = ['#a5bf6b', '#3f83bf']
34
+ scattercolor = ['green', 'blue']
35
35
  arts = []
36
36
  labels = []
37
37
 
@@ -74,15 +74,18 @@ def koschmieder(df: pd.DataFrame,
74
74
  label=f'Vis (km) = {round(coeff)} / Ext')
75
75
 
76
76
  arts.append(line)
77
- labels.append(f'Vis (km) = {round(coeff)} / Ext')
77
+ if 'dry' in ext_col:
78
+ labels.append(f'Vis (km) = {round(coeff)} / Ext (dry)')
79
+ else:
80
+ labels.append(f'Vis (km) = {round(coeff)} / Ext (amb)')
78
81
 
79
82
  ax.legend(handles=arts, labels=labels, loc='upper right', prop=dict(weight='bold'), bbox_to_anchor=(0.99, 0.99))
80
83
 
81
- ax.set(xlabel=kwargs.get('xlim', 'Visibility (km)'),
82
- ylabel=kwargs.get('xlim', 'Extinction (1/Mm)'),
83
- title=kwargs.get('ylim', 'Koschmieder relationship'),
84
+ ax.set(xlabel=kwargs.get('xlabel', 'Visibility (km)'),
85
+ ylabel=kwargs.get('ylabel', 'Extinction (1/Mm)'),
86
+ title=kwargs.get('title', 'Koschmieder relationship'),
84
87
  xlim=kwargs.get('xlim', (0, 30)),
85
- ylim=kwargs.get('ylim', (0, 500))
88
+ ylim=kwargs.get('ylim', (0, 800))
86
89
  )
87
90
 
88
91
  plt.xticks(ticks=np.array(range(0, 31, 5)), labels=np.array(range(0, 31, 5)))
@@ -348,7 +348,6 @@ def timeseries_stacked(df,
348
348
  ylabel=kwargs.get('ylabel', 'Percentage (%)'),
349
349
  xlim=kwargs.get('xlim', (st_tm, fn_tm)),
350
350
  ylim=(0, 100),
351
- title=kwargs.get('title', '')
352
351
  )
353
352
 
354
353
  xticks = kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=major_freq))
@@ -0,0 +1,117 @@
1
+ from datetime import datetime
2
+ from pathlib import Path
3
+
4
+ from pandas import Grouper, Timedelta
5
+
6
+ from AeroViz.rawDataReader.config.supported_instruments import meta
7
+ from AeroViz.rawDataReader.script import *
8
+
9
+ __all__ = ['RawDataReader']
10
+
11
+ SUPPORTED_INSTRUMENTS = [
12
+ NEPH, Aurora, SMPS, GRIMM, APS_3321, AE33, AE43, BC1054,
13
+ MA350, TEOM, OCEC, IGAC, VOC, EPA, Minion
14
+ ]
15
+
16
+
17
+ def RawDataReader(instrument_name: str,
18
+ path: Path,
19
+ reset: bool = False,
20
+ qc: bool | str = True,
21
+ qc_freq: str | None = None,
22
+ rate: bool = True,
23
+ append_data: bool = False,
24
+ start: datetime = None,
25
+ end: datetime = None,
26
+ mean_freq: str = '1h',
27
+ csv_out: bool = True,
28
+ ):
29
+ """
30
+ Factory function to instantiate the appropriate reader module for a given instrument and
31
+ return the processed data over the specified time range.
32
+
33
+ :param instrument_name: The name of the instrument for which to read data. Must be a valid key in the `meta` dictionary.
34
+ :param path: The directory where raw data files for the instrument are stored.
35
+ :param reset: If True, reset the state and reprocess the data from scratch.
36
+ :param qc: If True, apply quality control (QC) to the raw data.
37
+ :param qc_freq: Frequency at which to perform QC. Must be one of 'W', 'M', 'Q', 'Y' for weekly, monthly, quarterly, or yearly.
38
+ :param rate: If True, calculate rates from the data.
39
+ :param append_data: If True, append new data to the existing dataset instead of overwriting it.
40
+ :param start: Start time for filtering the data. If None, no start time filtering will be applied.
41
+ :param end: End time for filtering the data. If None, no end time filtering will be applied.
42
+ :param mean_freq: Resampling frequency for averaging the data. Example: '1h' for hourly mean.
43
+ :param csv_out: If True, output the processed data as a CSV file.
44
+
45
+ :return: An instance of the reader module corresponding to the specified instrument, which processes the data and returns it in a usable format.
46
+
47
+ :raises ValueError: If the `instrument_name` provided is not a valid key in the `meta` dictionary.
48
+ :raises ValueError: If the specified path does not exist or is not a directory.
49
+ :raises ValueError: If the QC frequency is invalid.
50
+ :raises ValueError: If start and end times are not both provided or are invalid.
51
+ :raises ValueError: If the mean_freq is not a valid frequency string.
52
+
53
+ :Example:
54
+
55
+ To read and process data for the BC1054 instrument:
56
+
57
+ >>> from pathlib import Path
58
+ >>> from datetime import datetime
59
+ >>>
60
+ >>> data = RawDataReader(
61
+ ... instrument_name='BC1054',
62
+ ... path=Path('/path/to/data'),
63
+ ... start=datetime(2024, 2, 1),
64
+ ... end=datetime(2024, 7, 31, 23))
65
+ """
66
+ # Mapping of instrument names to their respective classes
67
+ instrument_class_map = {cls.__name__.split('.')[-1]: cls for cls in SUPPORTED_INSTRUMENTS}
68
+
69
+ # Check if the instrument name is in the map
70
+ if instrument_name not in meta.keys():
71
+ raise ValueError(f"Instrument name '{instrument_name}' is not valid. \nMust be one of: {list(meta.keys())}")
72
+
73
+ # 檢查 path 是否存在且是一個目錄
74
+ if not isinstance(path, Path):
75
+ path = Path(path)
76
+ if not path.exists() or not path.is_dir():
77
+ raise ValueError(f"The specified path '{path}' does not exist or is not a directory.")
78
+
79
+ # Validate the QC frequency
80
+ if qc_freq is not None:
81
+ try:
82
+ Grouper(freq=qc_freq)
83
+ except ValueError as e:
84
+ raise ValueError(f"Invalid frequency: {qc_freq}. Error: {str(e)}")
85
+ except TypeError as e:
86
+ raise ValueError(f"Invalid frequency type: {qc_freq}. Frequency should be a string.")
87
+
88
+ if start and end:
89
+ if end.hour == 0 and end.minute == 0 and end.second == 0:
90
+ end = end.replace(hour=23)
91
+ else:
92
+ raise ValueError("Both start and end times must be provided.")
93
+ if end <= start:
94
+ raise ValueError(f"Invalid time range: start {start} is after end {end}")
95
+
96
+ # 驗證 mean_freq 的格式是否正確
97
+ try:
98
+ Timedelta(mean_freq)
99
+ except ValueError:
100
+ raise ValueError(
101
+ f"Invalid mean_freq: '{mean_freq}'. It should be a valid frequency string (e.g., '1H', '30min', '1D').")
102
+
103
+ # Instantiate the class and return the instance
104
+ reader_module = instrument_class_map[instrument_name].Reader(
105
+ path=path,
106
+ reset=reset,
107
+ qc=qc,
108
+ qc_freq=qc_freq,
109
+ rate=rate,
110
+ append_data=append_data
111
+ )
112
+ return reader_module(
113
+ start=start,
114
+ end=end,
115
+ mean_freq=mean_freq,
116
+ csv_out=csv_out,
117
+ )
@@ -95,6 +95,53 @@ meta = {
95
95
  },
96
96
  },
97
97
 
98
+ "XRF": {
99
+ "pattern": ["*.csv"],
100
+ "freq": "1h",
101
+ "deter_key": {
102
+ "Al": ["Al"],
103
+ "Si": ["Si"],
104
+ "P": ["P"],
105
+ "S": ["S"],
106
+ "Cl": ["Cl"],
107
+ "K": ["K"],
108
+ "Ca": ["Ca"],
109
+ "Ti": ["Ti"],
110
+ "V": ["V"],
111
+ "Cr": ["Cr"],
112
+ "Mn": ["Mn"],
113
+ "Fe": ["Fe"],
114
+ "Ni": ["Ni"],
115
+ "Cu": ["Cu"],
116
+ "Zn": ["Zn"],
117
+ "As": ["As"],
118
+ "Se": ["Se"],
119
+ "Br": ["Br"],
120
+ "Rb": ["Rb"],
121
+ "Sr": ["Sr"],
122
+ "Y": ["Y"],
123
+ "Zr": ["Zr"],
124
+ "Mo": ["Mo"],
125
+ "Ag": ["Ag"],
126
+ "Cd": ["Cd"],
127
+ "In": ["In"],
128
+ "Sn": ["Sn"],
129
+ "Sb": ["Sb"],
130
+ "Te": ["Te"],
131
+ "Cs": ["Cs"],
132
+ "Ba": ["Ba"],
133
+ "La": ["La"],
134
+ "Ce": ["Ce"],
135
+ "W": ["W"],
136
+ "Pt": ["Pt"],
137
+ "Au": ["Au"],
138
+ "Hg": ["Hg"],
139
+ "Tl": ["Tl"],
140
+ "Pb": ["Pb"],
141
+ "Bi": ["Bi"],
142
+ },
143
+ },
144
+
98
145
  "VOC": {
99
146
  "pattern": ["*.csv"],
100
147
  "freq": "1h",
@@ -116,32 +163,18 @@ meta = {
116
163
  "deter_key": None,
117
164
  },
118
165
 
119
- "Table": {
166
+ "EPA": {
120
167
  "pattern": ["*.csv"],
121
168
  "freq": "1h",
122
- "deter_key": None,
123
- },
124
-
125
- "EPA_vertical": {
126
- "pattern": ["*.csv"],
127
- "freq": "1h",
128
- "deter_key": None,
169
+ "deter_key": {"Items": ["all"]},
129
170
  },
130
171
 
131
172
  "Minion": {
132
- "pattern": ["*.csv"],
173
+ "pattern": ["*.csv", "*.xlsx"],
133
174
  "freq": "1h",
134
175
  "deter_key": {
135
- "Na+": ["Na+"],
136
- "NH4+": ["NH4+"],
137
- "K+": ["K+"],
138
- "Mg2+": ["Mg2+"],
139
- "Ca2+": ["Ca2+"],
140
- "Cl-": ["Cl-"],
141
- "NO2-": ["NO2-"],
142
- "NO3-": ["NO3-"],
143
- "SO42-": ["SO42-"],
144
- "Main Salt (NH4+, NO3-, SO42-)": ["NO3-", "SO42-", "NH4+"],
176
+ "Main Salt (Na+, NH4+, Cl-, NO3-, SO42-)": ["Na+", "NH4+", "Cl-", "NO3-", "SO42-"],
177
+ "XRF (Al, Ti, V, Cr, Mn, Fe)": ["Al", "Ti", "V", "Cr", "Mn", "Fe"],
145
178
  },
146
179
  },
147
180
  }