Skip to content

Commit 8a31eec

Browse files
Fixed a bug where VDA()-method would fail to find the date of event if datetimes were defined as numpy.datetime64. Also enlarged VDA() markers and fixed an issue where the method would crash with custom data coupled with SEPPY-data and user-defined y-errors.
1 parent 13707b6 commit 8a31eec

File tree

1 file changed

+78
-36
lines changed

1 file changed

+78
-36
lines changed

pyonset/__init__.py

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
5252
@Author: Christian Palmroos <[email protected]>
5353
54-
@Updated: 2024-11-25
54+
@Updated: 2024-12-04
5555
5656
Known problems/bugs:
5757
> Does not work with SolO/STEP due to electron and proton channels not defined in all_channels() -method
@@ -1780,6 +1780,10 @@ def VDA(self, onset_times=None, Onset=None, energy:str='gmean', selection=None,
17801780
stopreason (ODR), fig and axes.
17811781
"""
17821782

1783+
VDA_MARKERSIZE = 24
1784+
VDA_ELINEWIDTH = 2.2
1785+
VDA_CAPSIZE = 6.0
1786+
17831787
from scipy.stats import t as studentt
17841788

17851789
import numpy.ma as ma
@@ -1945,8 +1949,14 @@ def VDA(self, onset_times=None, Onset=None, energy:str='gmean', selection=None,
19451949
plus_err = pd.Timedelta(seconds=1)
19461950
minus_err = pd.Timedelta(seconds=1)
19471951

1948-
plus_errs = np.append(plus_errs, plus_err) if plus_err >= self.minimum_cadences[f"{spacecraft}_{instrument}"]/2 else np.append(plus_errs, pd.Timedelta(self.minimum_cadences[f"{spacecraft}_{instrument}"])/2)
1949-
minus_errs = np.append(minus_errs, minus_err) if minus_err >= self.minimum_cadences[f"{spacecraft}_{instrument}"]/2 else np.append(minus_errs, pd.Timedelta(self.minimum_cadences[f"{spacecraft}_{instrument}"])/2)
1952+
# Before a check was done here to make sure that the errors are not smaller than half of the minimum
1953+
# cadence of the respective instrument; this is now obsolete, as the errors are properly inspected
1954+
# while the weighting is performed.
1955+
# plus_errs = np.append(plus_errs, plus_err) if plus_err >= self.minimum_cadences[f"{spacecraft}_{instrument}"]/2 else np.append(plus_errs, pd.Timedelta(self.minimum_cadences[f"{spacecraft}_{instrument}"])/2)
1956+
# minus_errs = np.append(minus_errs, minus_err) if minus_err >= self.minimum_cadences[f"{spacecraft}_{instrument}"]/2 else np.append(minus_errs, pd.Timedelta(self.minimum_cadences[f"{spacecraft}_{instrument}"])/2)
1957+
plus_errs = np.append(plus_errs, plus_err)
1958+
minus_errs = np.append(minus_errs, minus_err)
1959+
19501960

19511961
for ch in channels1:
19521962

@@ -1959,8 +1969,8 @@ def VDA(self, onset_times=None, Onset=None, energy:str='gmean', selection=None,
19591969
plus_err1 = pd.Timedelta(seconds=1)
19601970
minus_err1 = pd.Timedelta(seconds=1)
19611971

1962-
plus_errs1 = np.append(plus_errs1, plus_err1) if plus_err1 >= Onset.minimum_cadences[f"{Onset.spacecraft}_{Onset.sensor}"]/2 else np.append(plus_errs1, pd.Timedelta(Onset.minimum_cadences[f"{Onset.spacecraft}_{Onset.sensor}"])/2)
1963-
minus_errs1 = np.append(minus_errs1, minus_err1) if minus_err1 >= Onset.minimum_cadences[f"{Onset.spacecraft}_{Onset.sensor}"]/2 else np.append(minus_errs1, pd.Timedelta(Onset.minimum_cadences[f"{Onset.spacecraft}_{Onset.sensor}"])/2)
1972+
plus_errs1 = np.append(plus_errs1, plus_err1)
1973+
minus_errs1 = np.append(minus_errs1, minus_err1)
19641974

19651975
plus_errs_all = np.append(plus_errs, plus_errs1)
19661976
minus_errs_all = np.append(minus_errs, minus_errs1)
@@ -1978,33 +1988,58 @@ def VDA(self, onset_times=None, Onset=None, energy:str='gmean', selection=None,
19781988
y_errors_all = np.array([minus_errs_secs_all, plus_errs_secs_all])
19791989

19801990
else:
1981-
# The y-directional error, which is the temporal uncertainty
1982-
try:
1991+
# The y-directional error was given by the user
1992+
# 4 arrays, so asymmetric plus and minus errors
1993+
if len(yerrs)==4:
19831994

1984-
reso_str = get_time_reso(self.flux_series)
1985-
reso_str1 = get_time_reso(Onset.flux_series)
1995+
plus_errs, minus_errs = np.array([]), np.array([])
1996+
plus_errs1, minus_errs1 = np.array([]), np.array([])
19861997

1987-
time_error = [pd.Timedelta(reso_str) for _ in range(len(x_errors))]
1988-
time_error1 = [pd.Timedelta(reso_str1) for _ in range(len(x_errors1))]
1998+
minus_timestamps, plus_timestamps = yerrs[0], yerrs[1]
1999+
minus_timestamps1, plus_timestamps1 = yerrs[0], yerrs[1]
19892000

1990-
except ValueError:
2001+
for i, ch in enumerate(channels):
19912002

1992-
# get_time_errors() attempts to manually extract the time errors of each data point
1993-
time_error = get_time_errors(onset_times=onset_times, spacecraft=spacecraft)
1994-
time_error1 = get_time_errors(onset_times=onset_times1, spacecraft=Onset.spacecraft.lower())
2003+
try:
2004+
minus_err = minus_timestamps[i]
2005+
plus_err = plus_timestamps[i]
2006+
except KeyError as e:
2007+
plus_err = pd.Timedelta(seconds=1)
2008+
minus_err = pd.Timedelta(seconds=1)
2009+
2010+
plus_errs = np.append(plus_errs, plus_err)
2011+
minus_errs = np.append(minus_errs, minus_err)
19952012

1996-
try:
1997-
_ = str(time_error[0].seconds)
1998-
# Indexerror is caused by all NaTs -> no point in doing VDA
1999-
except IndexError:
2000-
return None
2013+
y_errors_all = np.array([plus_errs,minus_errs])
20012014

2015+
# Convert errors in time to seconds
2016+
plus_errs_secs = [err.seconds for err in plus_errs]
2017+
minus_errs_secs = [err.seconds for err in minus_errs]
20022018

2003-
# These are for the fitting function
2004-
y_errors = [err.seconds for err in time_error]
2005-
y_errors1 = [err.seconds for err in time_error1]
2019+
y_errors_all_secs = np.array([plus_errs_secs,minus_errs_secs])
20062020

2007-
y_errors_all = np.concatenate((y_errors, y_errors1))
2021+
# 2 arrays -> symmetric errors for both
2022+
elif len(yerrs)==2:
2023+
2024+
# Check the first entry of yerrs. If it's not a timedelta, then it's the reach of the error
2025+
if not isinstance(yerrs[0][0], (pd.Timedelta, datetime.timedelta)):
2026+
y_errors_all = np.array([]) #= yerrs
2027+
2028+
for i, ch in enumerate(channels):
2029+
y_err = yerrs[i] - self.onset_statistics[ch][ref_idx]
2030+
y_errors_all = np.append(y_errors, y_err)
2031+
2032+
else:
2033+
# These are for the fitting function
2034+
y_errors = yerrs[0]
2035+
y_errors1 = yerrs[1]
2036+
2037+
y_errors_all = np.concatenate((y_errors, y_errors1))
2038+
y_errors_all_secs = [err.seconds for err in y_errors_all]
2039+
2040+
# 1 array -> wrong
2041+
else:
2042+
raise ValueError("The y-errors for two-instrument VDA must be in form of 4 or 2 lists!")
20082043

20092044
# Numpy masks work so that True values get masked as invalid, while False remains unaltered
20102045
mask = np.isnan(date_in_sec_all)
@@ -2016,17 +2051,18 @@ def VDA(self, onset_times=None, Onset=None, energy:str='gmean', selection=None,
20162051
inverse_beta_corrected = ma.array(inverse_beta_corrected, mask=mask)
20172052
x_errors_all = ma.array(x_errors_all, mask=mask)
20182053

2054+
# Asymmetric errors / errors not defined by the user
20192055
if len(y_errors_all)==2:
20202056
y_errors_plot = ma.array([minus_errs, plus_errs], mask=[np.isnan(date_in_sec),np.isnan(date_in_sec)])
20212057
y_errors1_plot = ma.array([minus_errs1, plus_errs1], mask=[np.isnan(date_in_sec1),np.isnan(date_in_sec1)])
20222058
y_errors_all_plot = ma.array([minus_errs_all, plus_errs_all], mask=[mask,mask])
20232059
y_errors_all_secs = ma.array([minus_errs_secs_all, plus_errs_secs_all], mask=[mask,mask])
20242060

20252061
else:
2026-
y_errors_plot = ma.array(time_error, mask=np.isnan(date_in_sec))
2027-
y_errors1_plot = ma.array(time_error1, mask=np.isnan(date_in_sec1))
2028-
y_errors_all_plot = ma.array(y_errors_all, mask=[mask,mask])
2029-
y_errors_all_secs = ma.array(y_errors_all, mask=mask)
2062+
y_errors_plot = ma.array(y_errors, mask=np.isnan(date_in_sec))
2063+
y_errors1_plot = ma.array(y_errors1, mask=np.isnan(date_in_sec1))
2064+
y_errors_all_plot = ma.array(y_errors_all, mask=mask)
2065+
y_errors_all_secs = ma.array(y_errors_all_secs, mask=mask)
20302066

20312067
# After masking NaNs and Nats away, slice
20322068
# which datapoints to consider for the fit
@@ -2338,25 +2374,24 @@ def VDA(self, onset_times=None, Onset=None, energy:str='gmean', selection=None,
23382374
if Onset and len(inverse_beta1)>0:
23392375
label1 = Onset.sensor.upper() if mass_energy==mass_energy1 else f"{Onset.sensor.upper()} {species_title1}"
23402376
ax.errorbar(inverse_beta1, onset_times1, yerr=y_errors1_plot, xerr=[x_errors_upper1, x_errors_lower1],
2341-
fmt='o', elinewidth=1.5, capsize=4.5, zorder=1, label=label1)
2377+
fmt='o', elinewidth=VDA_ELINEWIDTH, capsize=VDA_CAPSIZE, zorder=1, label=label1)
23422378

2343-
# The reason xerr seems to be wrong way is that 'upper' refers to the upper ENERGY boundary, which corresponds to the LOWER 1/beta boundary
23442379
if not Onset:
23452380
label = "onset times"
23462381
else:
23472382
label = self.sensor.upper() if mass_energy==mass_energy1 else f"{self.sensor.upper()} {species_title}"
23482383

23492384
if len(inverse_beta) > 0:
23502385
ax.errorbar(inverse_beta, onset_times, yerr=y_errors_plot, xerr=[x_errors_upper, x_errors_lower],
2351-
fmt='o', elinewidth=1.5, capsize=4.5, zorder=1, label=label)
2386+
fmt='o', elinewidth=VDA_ELINEWIDTH, capsize=VDA_CAPSIZE, zorder=1, label=label)
23522387

23532388
# Omitted datapoints, paint all points white and then those not omitted blue (+ red) again
23542389
if omitted_exists:
23552390
if Onset:
2356-
ax.scatter(inverse_beta1[selection1], onset_times1[selection1], s=11, zorder=3)
2391+
ax.scatter(inverse_beta1[selection1], onset_times1[selection1], s=VDA_MARKERSIZE, zorder=3)
23572392

2358-
ax.scatter(inverse_beta_all, onset_times_all, c="white", s=10, zorder=2)
2359-
ax.scatter(inverse_beta[selection], onset_times[selection], s=11, zorder=3)
2393+
ax.scatter(inverse_beta_all, onset_times_all, c="white", s=VDA_MARKERSIZE-5, zorder=2)
2394+
ax.scatter(inverse_beta[selection], onset_times[selection], s=VDA_MARKERSIZE, zorder=3)
23602395

23612396
# The odr fit
23622397
# Here we need to first take the selection of i_beta_all and ONLY after that take the compressed form, which is the set of valid values
@@ -5256,8 +5291,15 @@ def get_figdate(dt_array):
52565291
figdate = date
52575292
break
52585293

5259-
5260-
return figdate.date().strftime("%Y-%m-%d")
5294+
try:
5295+
date = figdate.date().strftime("%Y-%m-%d")
5296+
5297+
# An attributeerror is cause by figdate being numpy.datetime64 -object. handle
5298+
# it appropriately
5299+
except AttributeError:
5300+
date = np.datetime_as_string(figdate, unit='D')
5301+
5302+
return date
52615303

52625304

52635305
def _isnotebook():

0 commit comments

Comments
 (0)