fluxdataqaqc 0.3.2__tar.gz → 0.3.3__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.
- {fluxdataqaqc-0.3.2/fluxdataqaqc.egg-info → fluxdataqaqc-0.3.3}/PKG-INFO +1 -1
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Basic_usage/AMF_US-Tw3_BASE_HH_5-5.csv +1 -1
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/data_for_multiple_soil_vars.csv +1 -1
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc/__init__.py +1 -1
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc/qaqc.py +131 -219
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3/fluxdataqaqc.egg-info}/PKG-INFO +1 -1
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/tests/test_fluxdataqaqc.py +0 -93
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/LICENSE.txt +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/MANIFEST.in +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/README.rst +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/environment.yml +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Basic_usage/FLX_US-AR1_FLUXNET2015_SUBSET_DD_2009-2012_1-3.xlsx +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Basic_usage/US-Tw3_config.ini +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Basic_usage/USGS_config.ini +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Basic_usage/fluxnet_config.ini +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Basic_usage/raw_subhour_DVDV_10.xlsx +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/config_for_QC_flag_filtering.ini +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/config_for_multiple_soil_vars.ini +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/data_for_QC_flag_filtering.csv +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc/data.py +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc/plot.py +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc/util.py +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc.egg-info/SOURCES.txt +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc.egg-info/dependency_links.txt +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc.egg-info/requires.txt +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/fluxdataqaqc.egg-info/top_level.txt +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/requirements.txt +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/setup.cfg +0 -0
- {fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/setup.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
|
-
name: fluxdataqaqc-0.3.
|
|
2
|
+
name: fluxdataqaqc-0.3.3/examples/Basic_usage/AMF_US-Tw3_BASE_HH_5-5.csv
|
|
3
3
|
size: 38180927 bytes
|
|
4
4
|
sha256: 0febe82fef616bd6f4b7664a971d2e6316a4f2f42090a80e2c43279102bfd383
|
{fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/data_for_multiple_soil_vars.csv
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
|
-
name: fluxdataqaqc-0.3.
|
|
2
|
+
name: fluxdataqaqc-0.3.3/examples/Config_options/data_for_multiple_soil_vars.csv
|
|
3
3
|
size: 44901633 bytes
|
|
4
4
|
sha256: 4c372d81a79b2b0f474ef6184fc2d84e3b8d40520c798196b1633e640ece4861
|
|
@@ -275,7 +275,6 @@ class QaQc(Plot, Convert):
|
|
|
275
275
|
'ebr_corr',
|
|
276
276
|
'ebr_user_corr',
|
|
277
277
|
'ebc_cf',
|
|
278
|
-
'ebc_cf_method',
|
|
279
278
|
'ebr_5day_clim',
|
|
280
279
|
'flux',
|
|
281
280
|
'flux_corr',
|
|
@@ -941,11 +940,7 @@ class QaQc(Plot, Convert):
|
|
|
941
940
|
# rename columns to internal names
|
|
942
941
|
df = self._df.rename(columns=self.inv_map).copy()
|
|
943
942
|
# avoid including string QC flags because of float forcing on resample
|
|
944
|
-
|
|
945
|
-
numeric_cols = [
|
|
946
|
-
c for c in df.columns
|
|
947
|
-
if not '_qc_flag' in c and not c == 'ebc_cf_method'
|
|
948
|
-
]
|
|
943
|
+
numeric_cols = [c for c in df.columns if not '_qc_flag' in c]
|
|
949
944
|
sum_cols = [k for k,v in self.agg_dict.items() if v == 'sum']
|
|
950
945
|
# to avoid warning/error of missing columns
|
|
951
946
|
sum_cols = list(set(sum_cols).intersection(df.columns))
|
|
@@ -1143,13 +1138,10 @@ class QaQc(Plot, Convert):
|
|
|
1143
1138
|
|
|
1144
1139
|
Note:
|
|
1145
1140
|
The *ebr_corr* variable or energy balance closure ratio is
|
|
1146
|
-
calculated from the corrected versions of LE and H
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
applied to the raw H and LE, which is saved as *ebc_cf*. The
|
|
1151
|
-
method used to get each day's correction factor (1-4, see
|
|
1152
|
-
:ref:`Closure Methodologies`) is saved as *ebc_cf_method*.
|
|
1141
|
+
calculated from the corrected versions of LE and H independent
|
|
1142
|
+
of the method. When using the 'ebr' method the energy balance
|
|
1143
|
+
correction factor (what is applied to the raw H and LE) is left as
|
|
1144
|
+
calculated (inverse of ebr) and saved as *ebc_cf*.
|
|
1153
1145
|
|
|
1154
1146
|
See Also:
|
|
1155
1147
|
For explanation of the linear regression method see the
|
|
@@ -1703,25 +1695,8 @@ a_site Rn 6.99350781229883 1.552 1.054 0.943
|
|
|
1703
1695
|
"""
|
|
1704
1696
|
Energy balance ratio correction method for daily LE, H, EBR, and ET.
|
|
1705
1697
|
|
|
1706
|
-
Correct
|
|
1707
|
-
|
|
1708
|
-
described by Pastorello et al. (2020), with the additional limits on
|
|
1709
|
-
the correction factor and corrected LE used by ``flux-data-qaqc``:
|
|
1710
|
-
|
|
1711
|
-
1. calculate the daily correction factor, CF = (Rn - G) / (H + LE),
|
|
1712
|
-
and remove values outside of 1.5 times its interquartile range
|
|
1713
|
-
2. method 1, median CF in a +/- 7 day window with at least 5 values
|
|
1714
|
-
3. method 2, mean CF in a +/- 5 day window
|
|
1715
|
-
4. method 3, mean CF in +/- 5 days around the same day in the
|
|
1716
|
-
previous and next years
|
|
1717
|
-
5. method 4 (not in FLUXNET), +/- 5 day moving mean of the day of
|
|
1718
|
-
year mean CF from methods 1 and 2 over all years
|
|
1719
|
-
6. keep CF between 0.5 and 2 and multiply LE and H by CF, remove
|
|
1720
|
-
days where corrected LE is <= -100 or >= 850 w/m2
|
|
1721
|
-
|
|
1722
|
-
A CF outside of the limits from any method is not used and the next
|
|
1723
|
-
method may fill that day. The method used for each day is saved as
|
|
1724
|
-
"ebc_cf_method".
|
|
1698
|
+
Correct turblent fluxes to close energy balance using methods
|
|
1699
|
+
described by FLUXNET for daily H and LE.
|
|
1725
1700
|
|
|
1726
1701
|
Updates :attr:`QaQc.df` and :attr:`QaQc.variables` attributes with new
|
|
1727
1702
|
variables related to the corrections, e.g. LE_corr, ebr_corr, etc.
|
|
@@ -1733,68 +1708,129 @@ a_site Rn 6.99350781229883 1.552 1.054 0.943
|
|
|
1733
1708
|
:obj:`None`
|
|
1734
1709
|
|
|
1735
1710
|
"""
|
|
1711
|
+
# moving windows FLUXNET methods 1, 2 and 3
|
|
1712
|
+
window_1 = 15
|
|
1713
|
+
window_2 = 11
|
|
1714
|
+
half_win_1 = window_1 // 2
|
|
1715
|
+
half_win_2 = window_2 // 2
|
|
1716
|
+
|
|
1736
1717
|
# drop relavant calculated variables if they exist
|
|
1737
1718
|
self._df = _drop_cols(self.df, self._eb_calc_vars)
|
|
1738
1719
|
df = self._df.rename(columns=self.inv_map)
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1720
|
+
# check if mean G, or heat storage measurements exist
|
|
1721
|
+
vars_to_use = ['LE','H','Rn','G']
|
|
1722
|
+
for el in ['SH', 'SLE', 'SG']:
|
|
1723
|
+
if el in df.columns:
|
|
1724
|
+
vars_to_use.append(el)
|
|
1725
|
+
df = df[vars_to_use].astype(float).copy()
|
|
1726
|
+
|
|
1727
|
+
# make copy of original data for later
|
|
1728
|
+
orig_df = df[['LE','H','Rn','G']].astype(float).copy()
|
|
1729
|
+
orig_df['ebr'] = (orig_df.H + orig_df.LE) / (orig_df.Rn - orig_df.G)
|
|
1730
|
+
|
|
1731
|
+
# compute IQR to filter out extreme ebrs,
|
|
1732
|
+
df['ebr'] = (df.H + df.LE) / (df.Rn - df.G)
|
|
1733
|
+
Q1 = df['ebr'].quantile(0.25)
|
|
1734
|
+
Q3 = df['ebr'].quantile(0.75)
|
|
1752
1735
|
IQR = Q3 - Q1
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
#
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1736
|
+
# filter values between Q1-1.5IQR and Q3+1.5IQR
|
|
1737
|
+
mask = (df['ebr'] >= (Q1 - 1.5 * IQR)) & (df['ebr'] <= (Q3 + 1.5 * IQR))
|
|
1738
|
+
filtered = df[mask]
|
|
1739
|
+
# apply filter
|
|
1740
|
+
filtered_mask = filtered.index
|
|
1741
|
+
removed_mask = set(df.index) - set(filtered_mask)
|
|
1742
|
+
removed_mask = pd.to_datetime(list(removed_mask))
|
|
1743
|
+
df.loc[removed_mask] = np.nan
|
|
1744
|
+
|
|
1745
|
+
# FLUXNET methods 1 and 2 for filtering/smoothing ebr
|
|
1746
|
+
ebr = df.ebr.values
|
|
1747
|
+
df['ebr_corr'] = np.nan
|
|
1748
|
+
for i in range(len(ebr)):
|
|
1749
|
+
win_arr1 = ebr[i-half_win_1:i+half_win_1+1]
|
|
1750
|
+
win_arr2 = ebr[i-half_win_2:i+half_win_2+1]
|
|
1751
|
+
count = np.count_nonzero(~np.isnan(win_arr1))
|
|
1752
|
+
# get median of daily window1 if half window2 or more days exist
|
|
1753
|
+
if count >= half_win_2:
|
|
1754
|
+
val = np.nanpercentile(win_arr1, 50, axis=None)
|
|
1755
|
+
if abs(1/val) >= 2 or abs(1/val) <= 0.5:
|
|
1756
|
+
val = np.nan
|
|
1757
|
+
# if at least one day exists in window2 take mean
|
|
1758
|
+
elif np.count_nonzero(~np.isnan(win_arr2)) > 0:
|
|
1759
|
+
val = np.nanmedian(win_arr2)
|
|
1760
|
+
if abs(1/val) >= 2 or abs(1/val) <= 0.5:
|
|
1761
|
+
val = np.nan
|
|
1762
|
+
else:
|
|
1763
|
+
# assign nan for now, update with 5 day climatology
|
|
1764
|
+
val = np.nan
|
|
1765
|
+
# assign values if they were found in methods 1 or 2
|
|
1766
|
+
df.iloc[i, df.columns.get_loc('ebr_corr')] = val
|
|
1767
|
+
# make 5 day climatology of ebr for method 3
|
|
1768
|
+
# the cenetered window skips first and last 5 DOYs
|
|
1769
|
+
# so prepend and append first and last 5 days and loop...
|
|
1770
|
+
doy_ebr_mean=df['ebr_corr'].groupby(df.index.dayofyear).mean().copy()
|
|
1771
|
+
l5days = pd.Series(
|
|
1772
|
+
index=np.arange(-4,1), data=doy_ebr_mean.iloc[-5:].values)
|
|
1773
|
+
f5days = pd.Series(
|
|
1774
|
+
index=np.arange(367,372), data=doy_ebr_mean.iloc[:5].values)
|
|
1775
|
+
#doy_ebr_mean = doy_ebr_mean.append(f5days)
|
|
1776
|
+
doy_ebr_mean = pd.concat([doy_ebr_mean, f5days])
|
|
1777
|
+
doy_ebr_mean = pd.concat([l5days, doy_ebr_mean])
|
|
1778
|
+
ebr_5day_clim = pd.DataFrame(
|
|
1779
|
+
index=np.arange(1,367), columns=['ebr_5day_clim'])
|
|
1780
|
+
doy_ebr_mean = doy_ebr_mean.values
|
|
1781
|
+
for i in range(len(doy_ebr_mean)):
|
|
1782
|
+
# i = 0 which starts at prepended 5 days, shift window up
|
|
1783
|
+
win = doy_ebr_mean[i:i+2*half_win_2+1]
|
|
1784
|
+
count = np.count_nonzero(~np.isnan(win))
|
|
1785
|
+
# get 11 day moving window mean
|
|
1786
|
+
if i in ebr_5day_clim.index and count > 0:
|
|
1787
|
+
ebr_5day_clim.iloc[
|
|
1788
|
+
i-1, ebr_5day_clim.columns.get_loc('ebr_5day_clim')
|
|
1789
|
+
] = np.nanmean(win)
|
|
1790
|
+
ebr_5day_clim['DOY'] = ebr_5day_clim.index
|
|
1791
|
+
ebr_5day_clim.index.name = 'date'
|
|
1792
|
+
|
|
1793
|
+
# fill gaps of 11 or more days in filtered EBR with 5 day clim
|
|
1794
|
+
df['DOY'] = df.index.dayofyear
|
|
1795
|
+
# datetime indices of all remaining null elements
|
|
1796
|
+
null_dates = df.loc[df.ebr_corr.isnull(), 'ebr_corr'].index
|
|
1797
|
+
|
|
1798
|
+
merged = pd.merge(
|
|
1799
|
+
df, ebr_5day_clim, left_on='DOY', right_index=True
|
|
1768
1800
|
)
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
#
|
|
1779
|
-
|
|
1780
|
-
|
|
1801
|
+
# assign 5 day climatology of EBR
|
|
1802
|
+
merged.loc[null_dates,'ebr_corr'] =\
|
|
1803
|
+
merged.loc[null_dates,'ebr_5day_clim'].astype(float)
|
|
1804
|
+
# replace raw variables with unfiltered dataframe copy
|
|
1805
|
+
merged.LE = orig_df.LE
|
|
1806
|
+
merged.H = orig_df.H
|
|
1807
|
+
merged.Rn = orig_df.Rn
|
|
1808
|
+
merged.G = orig_df.G
|
|
1809
|
+
merged.ebr = orig_df.ebr
|
|
1810
|
+
# calculated corrected EBR to assign to ebr_corr (not EBC_CF),
|
|
1811
|
+
# save CFs as defined by fluxnet method, i.e. inverse of EBR
|
|
1812
|
+
merged['ebc_cf'] = 1/merged.ebr_corr
|
|
1813
|
+
# filter out CF that are >=2 or <=0.5 (absolute)
|
|
1814
|
+
merged.loc[
|
|
1815
|
+
(abs(merged.ebc_cf) >= 2) | (abs(merged.ebc_cf <= 0.5)), 'ebc_cf'
|
|
1816
|
+
] = np.nan
|
|
1817
|
+
# apply corrections to LE and H multiply by 1/EBR
|
|
1818
|
+
merged['LE_corr'] = merged.LE * merged.ebc_cf
|
|
1819
|
+
merged['H_corr'] = merged.H * merged.ebc_cf
|
|
1781
1820
|
# filter out any corrected LE that <= -100 or >= 850 w/m2
|
|
1782
|
-
# also removing corrected H and
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
df
|
|
1792
|
-
# 5 day climatology of the energy balance ratio (method 4)
|
|
1793
|
-
df['ebr_5day_clim'] = 1 / cf_clim
|
|
1794
|
-
df['flux_corr'] = df.LE_corr + df.H_corr
|
|
1821
|
+
# also removing corrected H, EBR, and EBC_CF
|
|
1822
|
+
merged.loc[
|
|
1823
|
+
(merged.LE_corr >= 850) | (merged.LE_corr <= -100), (
|
|
1824
|
+
'LE_corr', 'H_corr', 'ebr_corr', 'ebc_cf'
|
|
1825
|
+
)
|
|
1826
|
+
] = np.nan
|
|
1827
|
+
# compute EBR total turb flux
|
|
1828
|
+
merged['flux_corr'] = merged['LE_corr'] + merged['H_corr']
|
|
1829
|
+
|
|
1830
|
+
df = self._df.rename(columns=self.inv_map)
|
|
1795
1831
|
# other variables needed for plots using raw data
|
|
1796
|
-
df['flux'] = LE + H
|
|
1797
|
-
df['energy'] = Rn - G
|
|
1832
|
+
df['flux'] = merged.LE + merged.H
|
|
1833
|
+
df['energy'] = merged.Rn - merged.G
|
|
1798
1834
|
|
|
1799
1835
|
# corrected turbulent flux if given from input data
|
|
1800
1836
|
if set(['LE_user_corr','H_user_corr']).issubset(df.columns):
|
|
@@ -1805,6 +1841,12 @@ a_site Rn 6.99350781229883 1.552 1.054 0.943
|
|
|
1805
1841
|
flux_user_corr = 'flux_user_corr',
|
|
1806
1842
|
ebr_user_corr = 'ebr_user_corr'
|
|
1807
1843
|
)
|
|
1844
|
+
# grab select columns to merge into main dataframe
|
|
1845
|
+
cols = list(set(merged.columns).difference(df.columns))
|
|
1846
|
+
# join calculated data in
|
|
1847
|
+
merged = df.join(merged[cols], how='outer')
|
|
1848
|
+
# remove merge columns with suffix
|
|
1849
|
+
merged = merged.drop(columns=['DOY_x','DOY_y'])
|
|
1808
1850
|
|
|
1809
1851
|
self.variables.update(
|
|
1810
1852
|
energy = 'energy',
|
|
@@ -1815,144 +1857,14 @@ a_site Rn 6.99350781229883 1.552 1.054 0.943
|
|
|
1815
1857
|
ebr = 'ebr',
|
|
1816
1858
|
ebr_corr = 'ebr_corr',
|
|
1817
1859
|
ebc_cf = 'ebc_cf',
|
|
1818
|
-
ebc_cf_method = 'ebc_cf_method',
|
|
1819
1860
|
ebr_5day_clim = 'ebr_5day_clim'
|
|
1820
1861
|
)
|
|
1821
1862
|
|
|
1822
1863
|
# revert column names to user's
|
|
1823
|
-
self._df =
|
|
1864
|
+
self._df = merged.rename(columns=self.variables)
|
|
1824
1865
|
# update flag for other methods
|
|
1825
1866
|
self.corrected = True
|
|
1826
|
-
|
|
1827
|
-
@staticmethod
|
|
1828
|
-
def _ebc_cf_moving_windows(cf, half_win_1=7, half_win_2=5,
|
|
1829
|
-
min_count_1=5):
|
|
1830
|
-
"""
|
|
1831
|
-
Energy balance closure correction factors from moving windows,
|
|
1832
|
-
methods 1 and 2 of the FLUXNET2015 daily correction.
|
|
1833
|
-
|
|
1834
|
-
Method 1 is the median of a +/- 7 day window when it has at least 5
|
|
1835
|
-
values, otherwise method 2 is the mean of a +/- 5 day window.
|
|
1836
|
-
Windows are cut off at the start and end of the record. Assumes a
|
|
1837
|
-
continuous daily time series.
|
|
1838
|
-
|
|
1839
|
-
Arguments:
|
|
1840
|
-
cf (:obj:`pandas.Series`): daily correction factors with
|
|
1841
|
-
outliers removed (null).
|
|
1842
|
-
|
|
1843
|
-
Keyword Arguments:
|
|
1844
|
-
half_win_1 (int): default 7. Half width of the method 1 window
|
|
1845
|
-
[days].
|
|
1846
|
-
half_win_2 (int): default 5. Half width of the method 2 window
|
|
1847
|
-
[days].
|
|
1848
|
-
min_count_1 (int): default 5. Minimum number of values in the
|
|
1849
|
-
method 1 window.
|
|
1850
|
-
|
|
1851
|
-
Returns:
|
|
1852
|
-
cf_corr, cf_method (tuple): :obj:`pandas.Series` of correction
|
|
1853
|
-
factors and the method used (1 or 2), null where the windows
|
|
1854
|
-
have no values.
|
|
1855
|
-
"""
|
|
1856
|
-
cf_vals = cf.values
|
|
1857
|
-
cf_corr = np.full(len(cf_vals), np.nan)
|
|
1858
|
-
cf_method = np.full(len(cf_vals), np.nan)
|
|
1859
|
-
|
|
1860
|
-
for i in range(len(cf_vals)):
|
|
1861
|
-
win_1 = cf_vals[max(0, i - half_win_1):i + half_win_1 + 1]
|
|
1862
|
-
win_2 = cf_vals[max(0, i - half_win_2):i + half_win_2 + 1]
|
|
1863
|
-
count_1 = np.count_nonzero(~np.isnan(win_1))
|
|
1864
|
-
count_2 = np.count_nonzero(~np.isnan(win_2))
|
|
1865
|
-
# method 1, median of +/- 7 days
|
|
1866
|
-
if count_1 >= min_count_1:
|
|
1867
|
-
cf_corr[i] = np.nanmedian(win_1)
|
|
1868
|
-
cf_method[i] = 1
|
|
1869
|
-
# method 2, mean of +/- 5 days
|
|
1870
|
-
elif count_2 > 0:
|
|
1871
|
-
cf_corr[i] = np.nanmean(win_2)
|
|
1872
|
-
cf_method[i] = 2
|
|
1873
|
-
|
|
1874
|
-
return (
|
|
1875
|
-
pd.Series(cf_corr, index=cf.index),
|
|
1876
|
-
pd.Series(cf_method, index=cf.index)
|
|
1877
|
-
)
|
|
1878
|
-
|
|
1879
|
-
@staticmethod
|
|
1880
|
-
def _ebc_cf_adjacent_years(cf, half_win=5):
|
|
1881
|
-
"""
|
|
1882
|
-
Energy balance closure correction factors from the previous and next
|
|
1883
|
-
years, method 3 of the FLUXNET2015 daily correction.
|
|
1884
|
-
|
|
1885
|
-
For each day, the mean of the correction factors within +/- 5 days
|
|
1886
|
-
of the same day in the previous and next years. Assumes a
|
|
1887
|
-
continuous daily time series.
|
|
1888
|
-
|
|
1889
|
-
Arguments:
|
|
1890
|
-
cf (:obj:`pandas.Series`): daily correction factors with
|
|
1891
|
-
outliers removed (null).
|
|
1892
|
-
|
|
1893
|
-
Keyword Arguments:
|
|
1894
|
-
half_win (int): default 5. Half width of the window [days].
|
|
1895
|
-
|
|
1896
|
-
Returns:
|
|
1897
|
-
cf_adjacent (:obj:`pandas.Series`): correction factors, null
|
|
1898
|
-
where neither year has values.
|
|
1899
|
-
"""
|
|
1900
|
-
# sum and count of values in a +/- 5 day window around each day
|
|
1901
|
-
window = cf.rolling(2 * half_win + 1, center=True, min_periods=1)
|
|
1902
|
-
win_sum = window.sum()
|
|
1903
|
-
win_count = window.count()
|
|
1904
|
-
|
|
1905
|
-
# add windows from the same days one year before and after
|
|
1906
|
-
total = np.zeros(len(cf))
|
|
1907
|
-
count = np.zeros(len(cf))
|
|
1908
|
-
for years in (-1, 1):
|
|
1909
|
-
dates = cf.index + pd.DateOffset(years=years)
|
|
1910
|
-
total += win_sum.reindex(dates).fillna(0).values
|
|
1911
|
-
count += win_count.reindex(dates).fillna(0).values
|
|
1912
|
-
|
|
1913
|
-
cf_adjacent = np.full(len(cf), np.nan)
|
|
1914
|
-
has_values = count > 0
|
|
1915
|
-
cf_adjacent[has_values] = total[has_values] / count[has_values]
|
|
1916
|
-
|
|
1917
|
-
return pd.Series(cf_adjacent, index=cf.index)
|
|
1918
|
-
|
|
1919
|
-
@staticmethod
|
|
1920
|
-
def _ebc_cf_climatology(cf_corr, half_win=5):
|
|
1921
|
-
"""
|
|
1922
|
-
Day of year climatology of energy balance closure correction
|
|
1923
|
-
factors, used to fill gaps that remain after FLUXNET method 3.
|
|
1924
|
-
|
|
1925
|
-
The mean correction factor for each day of year over all years is
|
|
1926
|
-
smoothed with a +/- 5 day moving mean, windows near the start and
|
|
1927
|
-
end of the year wrap around to the other end.
|
|
1928
|
-
|
|
1929
|
-
Arguments:
|
|
1930
|
-
cf_corr (:obj:`pandas.Series`): daily correction factors from
|
|
1931
|
-
methods 1 and 2.
|
|
1932
|
-
|
|
1933
|
-
Keyword Arguments:
|
|
1934
|
-
half_win (int): default 5. Half width of the window [days].
|
|
1935
|
-
|
|
1936
|
-
Returns:
|
|
1937
|
-
cf_clim (:obj:`pandas.Series`): correction factor for each day of
|
|
1938
|
-
year, index is day of year from 1 to 366.
|
|
1939
|
-
"""
|
|
1940
|
-
doy_mean = cf_corr.groupby(cf_corr.index.dayofyear).mean()
|
|
1941
|
-
# include all days of year so windows line up with day of year
|
|
1942
|
-
doy_mean = doy_mean.reindex(np.arange(1, 367))
|
|
1943
|
-
|
|
1944
|
-
# wrap around the end of the year before the moving mean
|
|
1945
|
-
wrapped = pd.concat([
|
|
1946
|
-
doy_mean.iloc[-half_win:], doy_mean, doy_mean.iloc[:half_win]
|
|
1947
|
-
])
|
|
1948
|
-
cf_clim = wrapped.rolling(
|
|
1949
|
-
2 * half_win + 1, center=True, min_periods=1
|
|
1950
|
-
).mean()
|
|
1951
|
-
cf_clim = cf_clim.iloc[half_win:-half_win]
|
|
1952
|
-
cf_clim.index = np.arange(1, 367)
|
|
1953
|
-
|
|
1954
|
-
return cf_clim
|
|
1955
|
-
|
|
1867
|
+
|
|
1956
1868
|
def _bowen_ratio_correction(self):
|
|
1957
1869
|
"""
|
|
1958
1870
|
Create corrected/adjusted latent energy and sensible heat flux to
|
|
@@ -253,18 +253,6 @@ class TestData(object):
|
|
|
253
253
|
with pytest.raises(ValueError, match='latent_heat_flux_units'):
|
|
254
254
|
Data(config)
|
|
255
255
|
|
|
256
|
-
def test_soil_heat_flux_column_named_G(self, data, tmp_path):
|
|
257
|
-
# g_1 uses the same column as G, its units are in the config but
|
|
258
|
-
# not stored in Data.units, this was rejected in version 0.3.1
|
|
259
|
-
config = data['package_root_dir']/'examples'\
|
|
260
|
-
/'Config_options'/'config_for_QC_flag_filtering.ini'
|
|
261
|
-
config = _tmp_config(
|
|
262
|
-
config, tmp_path,
|
|
263
|
-
set_opts=[('DATA', 'g_1', 'G'), ('DATA', 'g_1_units', 'w/m2')]
|
|
264
|
-
)
|
|
265
|
-
q = QaQc(Data(config))
|
|
266
|
-
assert 'G' in q.df.rename(columns=q.inv_map).columns
|
|
267
|
-
|
|
268
256
|
def test_missing_units_for_plot_only_variable(self, data, tmp_path):
|
|
269
257
|
# potential shortwave units are only used for plot labels
|
|
270
258
|
config = data['package_root_dir']/'examples'\
|
|
@@ -621,87 +609,6 @@ class TestQaQc(object):
|
|
|
621
609
|
assert sw_pot.loc['2020-06-21 12:00'] > 0
|
|
622
610
|
assert sw_pot.max() > 0
|
|
623
611
|
|
|
624
|
-
def _daily_cf(self, values, start='2010-03-01'):
|
|
625
|
-
"""Daily correction factor series for energy balance tests"""
|
|
626
|
-
index = pd.date_range(start, periods=len(values), freq='D')
|
|
627
|
-
return pd.Series(values, index=index, dtype=float)
|
|
628
|
-
|
|
629
|
-
def test_ebc_cf_method_1_at_record_start(self):
|
|
630
|
-
# first days used to get no moving window value
|
|
631
|
-
cf = self._daily_cf([1.2] * 30)
|
|
632
|
-
cf.iloc[0] = 1.0
|
|
633
|
-
cf_corr, method = QaQc._ebc_cf_moving_windows(cf)
|
|
634
|
-
assert (method == 1).all()
|
|
635
|
-
# day 0 window is days 0-7, median of 1.0 and seven 1.2 values
|
|
636
|
-
assert np.isclose(cf_corr.iloc[0], 1.2)
|
|
637
|
-
|
|
638
|
-
def test_ebc_cf_method_2_is_mean(self):
|
|
639
|
-
# only 3 values within +/- 7 days, method 2 uses the mean
|
|
640
|
-
cf = self._daily_cf([np.nan] * 30)
|
|
641
|
-
cf.iloc[[10, 12, 13]] = [1.0, 1.1, 1.6]
|
|
642
|
-
cf_corr, method = QaQc._ebc_cf_moving_windows(cf)
|
|
643
|
-
assert method.iloc[11] == 2
|
|
644
|
-
assert np.isclose(cf_corr.iloc[11], np.mean([1.0, 1.1, 1.6]))
|
|
645
|
-
# no values within +/- 5 days
|
|
646
|
-
assert np.isnan(cf_corr.iloc[25]) and np.isnan(method.iloc[25])
|
|
647
|
-
|
|
648
|
-
def test_ebc_cf_method_3_previous_and_next_years(self):
|
|
649
|
-
cf = self._daily_cf([1.0] * 365 + [1.5] * 365 + [1.4] * 365,
|
|
650
|
-
start='2010-01-01')
|
|
651
|
-
adjacent = QaQc._ebc_cf_adjacent_years(cf)
|
|
652
|
-
# middle year, mean of previous (1.0) and next (1.4) years
|
|
653
|
-
assert np.isclose(adjacent.loc['2011-06-15'], 1.2)
|
|
654
|
-
# first year, only the next year exists
|
|
655
|
-
assert np.isclose(adjacent.loc['2010-06-15'], 1.5)
|
|
656
|
-
# values in a gap of the middle year come from the other years
|
|
657
|
-
cf.loc['2011-06-01':'2011-07-31'] = np.nan
|
|
658
|
-
adjacent = QaQc._ebc_cf_adjacent_years(cf)
|
|
659
|
-
assert np.isclose(adjacent.loc['2011-06-15'], 1.2)
|
|
660
|
-
|
|
661
|
-
def test_ebc_cf_climatology_lines_up_with_day_of_year(self):
|
|
662
|
-
# 200 days from March 1, climatology used to shift by record start
|
|
663
|
-
cf = self._daily_cf(np.linspace(0.8, 1.4, 200))
|
|
664
|
-
clim = QaQc._ebc_cf_climatology(cf)
|
|
665
|
-
doy = cf.index.dayofyear
|
|
666
|
-
day = cf.index[100]
|
|
667
|
-
expected = cf[(doy >= day.dayofyear - 5) & (doy <= day.dayofyear + 5)]
|
|
668
|
-
assert np.isclose(clim.loc[day.dayofyear], expected.mean())
|
|
669
|
-
# no data in January
|
|
670
|
-
assert np.isnan(clim.loc[15])
|
|
671
|
-
|
|
672
|
-
def test_ebc_cf_out_of_limits_falls_through(self, data, monkeypatch):
|
|
673
|
-
# method 3 values outside of 0.5-2 are not used, method 4 fills
|
|
674
|
-
config = data['package_root_dir']\
|
|
675
|
-
/'examples'/'Basic_usage'/'US-Tw3_config.ini'
|
|
676
|
-
q = QaQc(Data(config))
|
|
677
|
-
q.correct_data(et_gap_fill=False)
|
|
678
|
-
method_3_days = q.df.ebc_cf_method == 3
|
|
679
|
-
assert method_3_days.any()
|
|
680
|
-
monkeypatch.setattr(
|
|
681
|
-
QaQc, '_ebc_cf_adjacent_years',
|
|
682
|
-
staticmethod(lambda cf, half_win=5: cf * 0 + 2.5)
|
|
683
|
-
)
|
|
684
|
-
q = QaQc(Data(config))
|
|
685
|
-
q.correct_data(et_gap_fill=False)
|
|
686
|
-
methods = q.df.ebc_cf_method
|
|
687
|
-
assert not (methods == 3).any()
|
|
688
|
-
assert (methods[method_3_days] == 4).all()
|
|
689
|
-
|
|
690
|
-
def test_ebr_correction_outputs(self, data):
|
|
691
|
-
config = data['package_root_dir']\
|
|
692
|
-
/'examples'/'Basic_usage'/'US-Tw3_config.ini'
|
|
693
|
-
q = QaQc(Data(config))
|
|
694
|
-
q.correct_data(et_gap_fill=False)
|
|
695
|
-
df = q.df.rename(columns=q.inv_map)
|
|
696
|
-
methods = df.ebc_cf_method.dropna()
|
|
697
|
-
assert set(methods.unique()).issubset({1, 2, 3, 4})
|
|
698
|
-
cf = df.ebc_cf.dropna()
|
|
699
|
-
assert ((cf > 0.5) & (cf < 2)).all()
|
|
700
|
-
assert np.allclose(df.LE_corr, df.LE * df.ebc_cf, equal_nan=True)
|
|
701
|
-
assert np.allclose(df.ebr_corr, 1 / df.ebc_cf, equal_nan=True)
|
|
702
|
-
# method flag is daily only
|
|
703
|
-
assert 'ebc_cf_method' not in q.monthly_df.columns
|
|
704
|
-
|
|
705
612
|
def _qc_example_config(self, data, tmp_path, **kwargs):
|
|
706
613
|
"""Daily US-AR1 example copied to a temp dir (gridMET writes)"""
|
|
707
614
|
config = (
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/config_for_QC_flag_filtering.ini
RENAMED
|
File without changes
|
{fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/config_for_multiple_soil_vars.ini
RENAMED
|
File without changes
|
{fluxdataqaqc-0.3.2 → fluxdataqaqc-0.3.3}/examples/Config_options/data_for_QC_flag_filtering.csv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|