ukssdc
module.There are a few data structures used throughout the swifttools.ukssdc
module, for which this page is
the reference.
These are:
The first two appear in quite a few places, the last one is only present for GRBs. All three are explained and demonstrated below in some detail. In each case I first give a short explanation of the structure, then an example, and then a detailed walkthrough the contents†. My hope is that you can glance over the introduction and this will be enough that these make sense when you encounter them in the main documentation. The full details are there for only when you really need them. But please do read them before contacting me for help…
(† For the burst analyser dict
I do not give a walkthrough. Since that data structure is only used
for GRBs the walkthrough appears on the swifttools.ukssdc.data.GRB
page).
Light curves are all stored in a dict
following a specific structure, and referred to throughout
this documentation as a "light curve dict
". This, as the name suggests, is a Python dict
and
it contains the following keys:
dict
giving, for each light curve in Datasets
, the URL to the light curve file.There will also be a key for each entry in Datasets
, the value of which is the actual light curve, in the form of a pandas DataFrame
.
As you can see, not all of these keys need to exist for the object to be a light curve dict
, only the 'Datasets'
key, and a key for each entry in 'Datasets' are guaranteed to exist.
This structure is probably best demonstrated, and the values explained, with an example:
{
'Datasets': ['WT', 'WT_incbad', 'PC', 'PC_incbad', 'PCUL', 'PCUL_incbad', 'PCHard', 'PCSoft', 'PCHR'],
'Binning': 'Counts',
'TimeFormat': 'MET',
'T0': 672786064,
'URLs': {'WT_incbad':'https://www.swift.ac.uk/xrt_curves/01104343/WTCURVE.qdp', ...}
'WT_incbad': ...,
'PC_incbad': ...,
'PCUL_incbad': ...,
...
}
In the above I have suppressed some contents for readability. Now let's go through these in turn and see what they mean,
'Datasets': ['WT', 'WT_incbad', 'PC', 'PC_incbad', 'PCUL_incbad', 'PCHard', 'PCSoft', 'PCHR'],
The 'Datasets' key is just a list of all the light curves contained in the set; the above is not exhaustive, just intended to give a reasonable range. The format of the name is as follows:
$mode$what$nosys$incbad
Where the elements are:
So, for example, the dataset "WT" is the WT-mode light curve, with systematics and without the unreliable datapoints. "PCUL_incbad" is the PC-mode upper limits, including potentially unreliable datapoints.
For an explanation of the systematics and unreliable datapoints, please see the the light curve documentation.
'Binning': 'Counts',
The 'Binning' entry is a single string indicating how the light curve was binned. This will be one of the following:
'TimeFormat': 'MET',
The 'TimeFormat' entry tells you what the value in the Time columns actually mean. These can be
Note that MET values are determined by the spacecraft onboard clock; to convert this to a universal time system such as
a UT calendar date, MJD etc, requires knowledge of the spacecraft clock drift and the leap second history. We recommend
using the swifttime
tool included in HEASoft
, which fully corrects for these effects.
'T0': 672786064,
The 'T0' entry gives the zeropoint used for the light curve. It is in Swift Mission Elapsed Time, which is seconds since 2001 Jan 01 at 00:00:00 UTC (as counted by the spacecraft onboard clock). This is really only of interest if the 'TimeFormat' is 'MET' since the other values are absolute anyway.
Please do note the warning above about converting MET to absolute time systems.
'URLs' : {
'WT': 'https://www.swift.ac.uk/xrt_curves/01104343/WTCURVE.qdp',
'WT_incbad': 'https://www.swift.ac.uk/xrt_curves/01104343/WTCURVE.qdp',
'PC': 'https://www.swift.ac.uk/xrt_curves/01104343/PCCURVE.qdp',
'PC_incbad': 'https://www.swift.ac.uk/xrt_curves/01104343/PCCURVE_incbad.qdp',
'PCUL_incbad': 'https://www.swift.ac.uk/xrt_curves/01104343/PCUL_incbad.qdp',
'PCHard': 'https://www.swift.ac.uk/xrt_curves/01104343/PCHR.qdp',
'PCSoft': 'https://www.swift.ac.uk/xrt_curves/01104343/PCHR.qdp',
'PCHR': 'https://www.swift.ac.uk/xrt_curves/01104343/PCHR.qdp',
}
The 'URLs' entry is a dict
with a key for each of the datasets downloaded (and given in 'Datasets'). This points to the
raw light curve data file from which the DataFrame
s were built, so you can readily get at the original files yourself if you wish.
And lastly we come to what you've been waiting patiently for: the actual light curves.
These are simply pandas DataFrame
objects containing the light curve data. The columns depends on the light curve type,
and some examples (with the rows truncated) are given below. If you want full details of what each row means,
you'll need to see the light curve documentation.
First, here's an example of a normal light curve (here, 'PC_incbad'):
Time | TimePos | TimeNeg | Rate | RatePos | RateNeg | FracExp | BGrate | BGerr | CorrFact | CtsInSrc | BGInSrc | Exposure | Sigma | SNR | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 231.329 | 9.456 | -10.603 | 2.492634 | 0.547598 | -0.547598 | 1.000000 | 0.006904 | 0.003088 | 2.396688 | 21.0 | 0.138491 | 20.058508 | 336.828549 | 4.551939 |
1 | 254.712 | 8.638 | -13.928 | 2.458382 | 0.502403 | -0.502403 | 1.000000 | 0.001227 | 0.001227 | 2.314148 | 24.0 | 0.027698 | 22.565840 | 865.481752 | 4.893247 |
2 | 272.685 | 10.724 | -9.335 | 2.265870 | 0.510246 | -0.510246 | 1.000000 | 0.006904 | 0.003088 | 2.288348 | 20.0 | 0.138491 | 20.058540 | 320.682615 | 4.440743 |
… |
An upper limit is similar, except that the "Rate" column is replaced with "UpperLimit":
Time | TimePos | TimeNeg | UpperLimit | RatePos | RateNeg | FracExp | BGrate | BGerr | CorrFact | CtsInSrc | BGInSrc | Exposure | Sigma | SNR | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 246742.809 | 172770.106 | -96881.981 | 0.001821 | 0.0 | 0.0 | 0.0364 | 0.000125 | 0.000005 | 1.6836 | 3.0 | 1.2247 | 9811.0649 | 1.4103 | inf |
The individual hard/soft band data are simpler and have symmetric errors on the rate:
Time | TimePos | TimeNeg | Rate | RateErr | |
---|---|---|---|---|---|
0 | 243.520 | 19.831 | -22.794 | 1.323371 | 0.271083 |
1 | 292.350 | 31.176 | -29.000 | 0.772638 | 0.174978 |
2 | 344.898 | 21.252 | -21.372 | 1.128383 | 0.252669 |
3 | 396.489 | 32.344 | -30.339 | 0.752788 | 0.168329 |
4 | 472.663 | 56.463 | -43.830 | 0.469917 | 0.105224 |
5 | 594.830 | 69.690 | -65.705 | 0.347482 | 0.078028 |
6 | 704.853 | 57.453 | -40.332 | 0.478983 | 0.107709 |
7 | 823.036 | 74.665 | -60.730 | 0.356802 | 0.078279 |
8 | 981.211 | 97.016 | -83.510 | 0.260160 | 0.058751 |
9 | 1169.651 | 66.536 | -91.425 | 0.294099 | 0.066134 |
10 | 1323.066 | 81.111 | -86.879 | 0.305241 | 0.065412 |
11 | 1511.165 | 146.250 | -106.988 | 0.183356 | 0.041583 |
12 | 1762.130 | 148.523 | -104.715 | 0.193502 | 0.040547 |
13 | 6366.778 | 235.123 | -253.803 | 0.075101 | 0.016944 |
14 | 7199.183 | 603.718 | -597.281 | 0.045383 | 0.008396 |
And the hardness ratios are likewise simple:
Time | TimePos | TimeNeg | HR | HRErr | |
---|---|---|---|---|---|
0 | 243.520 | 19.831 | -22.794 | 1.149509 | 0.344784 |
1 | 292.350 | 31.176 | -29.000 | 0.566709 | 0.160379 |
2 | 344.898 | 21.252 | -21.372 | 0.801010 | 0.240731 |
3 | 396.489 | 32.344 | -30.339 | 0.775909 | 0.231541 |
4 | 472.663 | 56.463 | -43.830 | 0.680760 | 0.197203 |
5 | 594.830 | 69.690 | -65.705 | 0.562516 | 0.157879 |
6 | 704.853 | 57.453 | -40.332 | 0.697443 | 0.203879 |
7 | 823.036 | 74.665 | -60.730 | 0.809213 | 0.239284 |
8 | 981.211 | 97.016 | -83.510 | 0.737809 | 0.219589 |
9 | 1169.651 | 66.536 | -91.425 | 0.681327 | 0.199567 |
10 | 1323.066 | 81.111 | -86.879 | 1.048259 | 0.322824 |
11 | 1511.165 | 146.250 | -106.988 | 0.838212 | 0.258235 |
12 | 1762.130 | 148.523 | -104.715 | 0.925555 | 0.267684 |
13 | 6366.778 | 235.123 | -253.803 | 0.942488 | 0.298569 |
14 | 7199.183 | 603.718 | -597.281 | 0.688193 | 0.165654 |
The spectrum dict
contains, at core, information about extracted spectra and (if applicable) the fitted model(s).
An extra complexity arises because a given object will often have multiple spectra. To accommodate this the spectral
dict
has, in all cases, three layers:
Even in cases where there is only one entry at a given level (e.g. there are only PC-mode data) all layers exist.
There are various relevant keywords in each layer as well. So, the conceptual map of a spectrum dict
is as follows:
dict
for each entry in 'rname' which contains:
dict
for each entry in 'Modes' which contains:
dict
for each entry in models, giving details of the spectral fit.Entries in boldface are lists, which give all the keys in the next layer.
Reading this in the abstract makes it looks rather more complex than it really is, and although I'll give an example
in a second, that en bloc is not really as readable as could be. So, perhaps the best way to explain
the concept is to show how you would actually access the spectral dict
.
For GRBs,
there are normally two time slices, which (somewhat unhelpfully) are called "interval0" and "late_time", and only a power-law model fit. If I have the spectrum dict
for some GRB in the variable mySpec
, and
I want to know what the best-fitting spectral index was for a power-law fit to the WT model "interval0" spectrum, I can just do:
print(mySpec['interval0']['WT']['Powerlaw']['Gamma])
This is somewhat more readable (I hope). There are a lot of layers but the layout is clear and, as you may have noticed, the order of the keys is just the inverse of the order of my prosaic statement, which, to me at least, seems logical. Of course, if you want something beyond a spectral fit parameter then you need to check at what level it is relevant, but hopefully this is also logical (and you can check it on the list above). For example, the exposure by the "late_time" PC mode spectrum doesn't depend on which model was fitted, so this value appears at the "rname/mode" level.
So, to unpack this a bit more, I will give below a real spectrum dict
for a GRB, and then I'll move on to the detailed expanation of each key.
One important note first: In some cases it may be that the automated spectral fit failed, or you disabled fitting; sometimes the spectrum for a given time interval could not be produced at all. In these cases the contents of the various layers differ, as discussed further down this page.
Right, let's take a look at a real spectrum dict
. This is GRB 130925A, if you're interested, and I've added some
blank lines and indentation to try to make it a bit more readable.
{ 'T0': 401775096,
'DeltaFitStat': 2.706,
'rnames': ['interval0', 'late_time'],
'interval0':{
'DataFile': 'https://www.swift.ac.uk/xrt_spectra/00571830/interval0.tar.gz',
'Start': 151.206299126148,
'Stop': 40413.0104033947,
'Modes': ['WT', 'PC'],
'WT': {
'Models': ['PowerLaw'],
'PowerLaw': {
'GalacticNH': 1.74728e+20,
'NH': 2.61822e+22,
'NHPos': 4.195329499999989e+20,
'NHNeg': -4.114560899999997e+20,
'Redshift_abs': 0.347,
'Gamma': 1.74059,
'GammaPos': 0.01495348799999996,
'GammaNeg': -0.014803381000000115,
'ObsFlux': 3.2308738407058433e-09,
'ObsFluxPos': 2.0927980842864315e-11,
'ObsFluxNeg': -2.0775434446476415e-11,
'UnabsFlux': 5.272662824293303e-09,
'UnabsFluxPos': 4.9185968693846606e-11,
'UnabsFluxNeg': -4.730966359732954e-11,
'Cstat': 1389.742568,
'Dof': 960,
'FitChi': 1347.730724,
'Image': 'https://www.swift.ac.uk/xrt_spectra/00571830/interval0wt_plot.gif'
},
'Exposure': 3162.851695179939,
'MeanTime': 3449.54543888569
},
'PC': {
'Models': ['PowerLaw'],
'PowerLaw': {
'GalacticNH': 1.74728e+20,
'NH': 3.05251e+22,
'NHPos': 3.1388244100000005e+21,
'NHNeg': -2.9426779299999997e+21,
'Redshift_abs': 0.347,
'Gamma': 2.59379,
'GammaPos': 0.1405418380000003,
'GammaNeg': -0.13220066099999972,
'ObsFlux': 7.526620170564824e-11,
'ObsFluxPos': 3.860703494878197e-12,
'ObsFluxNeg': -3.6410524464384235e-12,
'UnabsFlux': 2.772043237577702e-10,
'UnabsFluxPos': 5.272611683802778e-11,
'UnabsFluxNeg': -3.991673128151661e-11,
'Cstat': 487.9061045,
'Dof': 490,
'FitChi': 496.7632034,
'Image': 'https://www.swift.ac.uk/xrt_spectra/00571830/interval0pc_plot.gif'
},
'Exposure': 5029.643800079823,
'MeanTime': 17427.6717456579
}
},
'late_time': {
'DataFile': 'https://www.swift.ac.uk/xrt_spectra/00571830/late_time.tar.gz',
'Start': 5502.69384342432,
'Stop': 40413.0104033947,
'Modes': ['PC'],
'PC': {
'Models': ['PowerLaw'],
'PowerLaw': {
'GalacticNH': 1.74728e+20,
'NH': 3.15504e+22,
'NHPos': 3.276992999999999e+21,
'NHNeg': -3.0701709200000014e+21,
'Redshift_abs': 0.347,
'Gamma': 2.68868,
'GammaPos': 0.14820270499999966,
'GammaNeg': -0.13902135400000004,
'ObsFlux': 6.826529950876817e-11,
'ObsFluxPos': 3.594728145422862e-12,
'ObsFluxNeg': -3.3616900080457612e-12,
'UnabsFlux': 2.852200160603111e-10,
'UnabsFluxPos': 6.096089137778289e-11,
'UnabsFluxNeg': -4.542858750653171e-11,
'Cstat': 450.7716251,
'Dof': 470,
'FitChi': 458.30322,
'Image': 'https://www.swift.ac.uk/xrt_spectra/00571830/late_timepc_plot.gif'
},
'Exposure': 4966.961300075054,
'MeanTime': 18497.190929234
}
}
}
Yowsers that's a lot to take in, but hopefully you can follow the structure. First, we had some information common
to all the spectra: the reference time and delta fitstat (discussed in a moment). Then the "rnames" list told us
that we have two spectra, "interval0" and "late_time". If you follow the indentation levels you will see that
the remaining two keys in this top-level dict
were indeed "interval0" and "late_time". Within each of these
there was some information common to that spectral time-interval, and then "Modes", telling us for which modes
spectra were produced in this time interval. Then we have the keys "WT" and "PC" in the "interval0" spectrum, or just
"PC" for the "late_time" spectrum, and so on through the structure.
Honestly, that's probably (more than) enough to be going on with. You may want to glance at When data are missing, for completeness, but unless you're having trouble sleeping, you should only read on when you want more detail about a specific entry. If you are having trouble sleeping, make sure you use a blue-light filter on the device you're reading with.
OK, let's go through the actual keys one at a time. Yay.
First, the top-level dict
.
'T0' is a reference time for the spectrum in Swift Mission Elapsed Time. All other times are given relative to this. Please do read the notes on MET, above.
'DeltaFitStat' relates to the errors on the fitted parameters. These are determined by xspec
by
stepping the parameter of interest and refitting until the fit statistic has increased 'DeltaFitStat'.
Ordinarily this is either 1 (or 1-σ errors) or 2.706 (for 90% confidence errors), but in some cases
you can set this to whatever you want.
The 'GalNH_unfitted' value only appears for spectra where a Galactic absorber was not used. In these cases the expected
Galactic column along the line of sight to the source is given in the top level of the spectrum dict
. It is given
purely for information - it was not used at all - and was taken from Willingale et al.,
(2013).
'rnames' is a list giving the labels applied to each time interval for which spectra were built. This essentially serves as an index for the results. (NB 'rname' comes from 'region name', where a 'region' is a time region requested. Had I realised, when writing the spectral code in 2008, that one day I was going to expose its workings via an API, I would have used a better label).
That is it for the standard contents of the top level. There will still be one key for each entry in 'rnames', containing the details of what was produced for that time interval. So, let's go through the contents of each of these now.
The 'DataFile' entry contains a URL pointing to a '.tar.gz' archive which contains all of the files for the
spectra for this time interval. This can be used by you to download the spectrum if you want to fit or manipulate it
yourself. It is also used by the saveSpectra() function that appears throughout
the swifttools.ukssdc
module.
The 'Start' entry contains the start time in seconds since 'T0' of the requested time interval. This does not necessarily correspond to the time covered by any of the actual spectra, since that depends on the availability of data.
The 'Stop' entry contains the stop time in seconds since 'T0' of the requested time interval. This does not necessarily correspond to the time covered by any of the actual spectra, since that depends on the availability of data.
If no spectra could actually be created for this time interval, then the key 'NOSPEC' will exist, and the dict
for this
time interval will terminate here.
The 'Modes' key contains a list of the XRT modes for which spectra were created for this time interval.
And this is again, the end of the layer, the above defines the standard contents for each time interval. But of course, there is now an entry for each of the modes (listed in 'Modes') for which we have a spectrum. So, let's move on...
For each mode within each time interval, the dict
will have these keys:
The 'MeanTime' entry contains the mean time of the X-ray events in the spectrum for this time interval and mode. It is in seconds since T0.
The 'Exposure' entry gives the exposure time, in seconds, in the spectrum for this time interval and mode.
If, when requesting spectra be built, you also requested that they not be fitted automatically then there will be the 'NOFIT' key at this level and (strangely enough) no further information, since all that remains pertains to the automated fits.
The 'Models' key contains a list of the spectral models that were requested for fitting.
And we are now, finally, almost at an end. The only thing that remains at this level (time interval -> XRT Mode) is
one entry for each model that was requested for automatic fitting. These have keys given in the 'Models' entry just described,
and they are themselves dict
s. The keys of these dict
s depend upon the model, you can see from the example
earlier a typical example: for a blackbody or APEC fit "Gamma" will be replaced with "kT" and whether or not a redshift
accompanies the absorber depends on the request via which the spectrum was built.
Sometimes the automated spectral fit fails. In this case there will be a 'NOFIT' entry at this level.
There is also a key "Image" which gives the URL to an image of the spectrum and fitted model.
Phew. If you're stll awake, well done!
One very last thing to note. Sometimes, things can be missing. Below I have listed what can happen,
and what the spectrum dict
looks like in these cases.
dict
contains the key "NoSpectrum". The other keys are absent.dict
for the given time interval contains the key "NOSPEC"; the "Modes" key is absent.dict
for each time interval and mode contains the key "NOFIT"; the "Models" key is absent.
You may wonder why this isn't just given at the top level: the reason is that an [rname][mode] tree
still exists and contains all of the information about the spectrum, so it seems logical to indicate
that there is no fit at this level, where you will be otherwise looking for the "Models" entry.dict
for the specific time interval, model and model contains the key "NOFIT"; the results
of the fit are (obviously) absent.The burst analyser dict
is a nested dict
with several layers, just like the structures above.
The overall structure is
but things are slightly more complicated; the "BAT binning" layer is only present in one of the "Instrument" layers (the
BAT) and hardness ratio data move around a bit. The full schematic is below, and I have skipped some of the data
(especially all the actual light curves) and added some comments to make this more readable. For a walkthrough of the
contents, see the swifttools.ukssdc.data.GRB
page). The main point to note is that
this structure basically holds a lot of light curve dict
s, organised by Swift instrument,
(by binning for BAT only), and by energy band. For BAT and XRT there is also a hardness ratio time series (which includes
the photon index and ECF inferred from the hardness ratio).
{
'Instruments': ['BAT', 'BAT_NoEvolution', 'XRT', 'UVOT'],
'BAT': {
'HRData': <a DataFrame containing the hardness ratio>,
'Binning': [
'SNR4',
'SNR4_sinceT0',
'SNR5',
'SNR5_sinceT0',
'SNR6',
'SNR6_sinceT0',
'SNR7',
'SNR7_sinceT0',
'TimeBins_4ms',
'TimeBins_64ms',
'TimeBins_1s',
'TimeBins_10s'],
'SNR4': {
'Datasets': ['ObservedFlux', 'Density', 'XRTBand', 'BATBand'],
'ObservedFlux': <a DataFrame containing the light curve>,
'Density': <a DataFrame containing the light curve>,
'XRTBand': <a DataFrame containing the light curve>,
'BATBand': <a DataFrame containing the light curve>
}
'SNR4_sinceT0': {
'Datasets': ['ObservedFlux', 'Density', 'XRTBand', 'BATBand'],
'ObservedFlux': <a DataFrame containing the light curve>,
'Density': <a DataFrame containing the light curve>,
'XRTBand': <a DataFrame containing the light curve>,
'BATBand': <a DataFrame containing the light curve>
}
... more entries, as above, one for each entry in 'Binning'
}, # End of ['BAT']
'BAT_NoEvolution': {
'ECFs': {
'ObservedFlux': 0.00243424007095474,
'Density': 0.00231776220799315,
'XRTBand': 4.70354458026304e-08,
'BATBand': 6.85253672780114e-07
},
'Binning': [
'SNR4',
'SNR4_sinceT0',
'SNR5',
'SNR5_sinceT0',
'SNR6',
'SNR6_sinceT0',
'SNR7',
'SNR7_sinceT0',
'TimeBins_4ms',
'TimeBins_64ms',
'TimeBins_1s',
'TimeBins_10s']
'SNR4': {
'Datasets': ['ObservedFlux', 'Density', 'XRTBand', 'BATBand'],
'ObservedFlux': <a DataFrame containing the light curve>,
'Density': <a DataFrame containing the light curve>,
'XRTBand': <a DataFrame containing the light curve>,
'BATBand': <a DataFrame containing the light curve>
}
'SNR4_sinceT0': {
'Datasets': ['ObservedFlux', 'Density', 'XRTBand', 'BATBand'],
'ObservedFlux': <a DataFrame containing the light curve>,
'Density': <a DataFrame containing the light curve>,
'XRTBand': <a DataFrame containing the light curve>,
'BATBand': <a DataFrame containing the light curve>
}
... more entries, as above, one for each entry in 'Binning'
}, # End of ['BAT_NoEvolution']
'XRT': {
'HRData_PC': <a DataFrame containing the hardness ratio>
'Datasets': [
'ObservedFlux_PC_incbad',
'Density_PC_incbad',
'XRTBand_PC_incbad',
'BATBand_PC_incbad'
],
'ObservedFlux_PC_incbad': <a DataFrame containing the light curve>,
'Density_PC_incbad': <a DataFrame containing the light curve>,
'XRTBand_PC_incbad': <a DataFrame containing the light curve>,
}, , # End of ['XRT']
'UVOT': {
'Datasets': ['white', 'b', 'u', 'v', 'uvw1', 'uvw2', 'uvm2']
'white': <a DataFrame containing the light curve>,
'b': <a DataFrame containing the light curve>,
'u': <a DataFrame containing the light curve>,
'v': <a DataFrame containing the light curve>,
'uvw1': <a DataFrame containing the light curve>,
'uvw2': <a DataFrame containing the light curve>
}
}