Skip banner and navigation tools.

 |  site map

The swifttools.ukssdc.query module

Jupyter notebook version of this page

Latest version v1.0, released in swifttools v3.0

The query module provides tools to query various catalogues held at the UKSSDC (and a couple of others) and provides wrappers to get data for the objects we find. There are two types of query supported, a cone search and a more complex search applying filters to different fields. These are analogous to the 'simple' and 'advanced' searches available on the website.

Unlike the the data module (which provided functions to get specific products), the query module provided classes: we create query objects and then manipulate and use these to perform our query and play with the results. There are three query classes provided:

Whichever class you want, I strongly advise you to read this page first. Although it deals with the ObsQuery class, that class only contains functionality common to all of the classes, and so all the common concepts are introduced here, and not necessarily repeated on the other pages.

The query module also provides access to data or data products, by wrapping functions from the data module.I will not go into much detail about those functions and what they produce, since this has been covered in the relevant pages for the data module already. One crucial point, however, does need discussing: how the query module makes any requested data available to you (if you haven't read about the data module, ignore this point because it will just confuse you - you'll see how we get data shortly). In the data module the default behaviour when getting a product was to download files to disk, but you could instead request the data be returned from the function so you could capture it in a variable. For the query module, the requested data are always stored in variables inside your query object. You can still also request that they be saved to disk, or returned from the function that got them (using the same returnData and saveData arguments as in the data module) but these are not done by default.

In this notebook we will demonstrate the query interface for observation data, which is quite straightforward and also gets us familiar with the module syntax.

We will import the query module as uq:

import swifttools.ukssdc.query as uq

Contents

The ObsQuery Class

The ObsQuery class allows us to query the Swift observation database, it is the API equivalent of this UKSSDC webpage. The only functionality it provides is that which is common to the entire swifttools.ukssdc.query module and all subclasses, so it's the perfect one to use as an introduction.

As discussed above, this module is built around classes, so the first thing we have to do is create an ObsQuery object:

q = uq.ObsQuery(silent=False)
Resetting query details

I set silent=False because in an interactive case like this, it can be helpful to get some textual feedback. If you want even more feedback you can set verbose=True. These can be set in the constructor, or via simple calls:

q.verbose = True
q.verbose = False # Turn it off again!

Selecting a table

There are several database tables relating to Swift observations. Just like the website, the API gives you the most useful (I think) one by default, but of course, you can change this. But how? Well, first, we want to know what tables we have to choose from, and this is stored in the tables variable of our query object:

q.tables
('swiftmastr', 'swiftxrlog', 'swiftbalog', 'swiftuvlog', 'swifttdrss')

We can also check which one is currently selected, or change it, via the table variable:

q.table
'swiftmastr'
q.table = 'swiftxrlog'
Resetting query details

You'll notice the cell that changed the table warned us that it was resetting the query details. This only appeared because we have silent=False and don't worry about it too much now. Basically it is warning us that any filters set or results retrieved (all covered below) have been wiped because we changed table. We could also have set the table we wanted in the constructor:

q = uq.ObsQuery(table='swiftbalog',
                silent=False
               )
q.table
Resetting query details

'swiftbalog'

I won't keep saying this but I'll remind you here: ObsQuery only contains behaviour common to the entire query module, so the syntax for checking and changing tables is the same for the GRBQuery and SXPSQuery modules (and for any others I may add in the future).

Simple (cone) searches

A simple search is just a cone search, and rather than pontificating, let's just demonstrate. I will stick with the default table, "swiftmastr" for all the demonstrations below.

q = uq.ObsQuery(silent=False)
q.addConeSearch(name='GK Per', radius=300, units='arcsec')
q.submit()
Resetting query details
Need to get the metadata to check the query is valid.
Calling DB look-up for rows 0 -- 1000
Resolved 'GK Per' as (52.80004119305, 43.90429720867) via SIMBAD
Received 145 rows.

That was pretty straightforward wasn't it? We introduced two functions here, addConeSearch() and submit(). The latter is very easy: it takes no arguments and just submits our query object for execution.

addConeSearch() should also be pretty clear, but I'll elaborate on its arguments in a moment.

Because we disabled silent mode, we got a bit of information about what was going on, and we can see, firstly, that "GK Per" was resolved into coordinates (thank you SIMBAD), and secondly, that we got 145 results. But where are those results? They are stored inside your query object (q) and the variable holding them is cunningly named results. This is a pandas DataFrame and we can have a look at it:

q.results
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
0 10.171878 GKPer 30842 52.796556 43.903002 254.995416 2010-03-09T00:11:00 2010-03-09T01:29:57 27 00030842027 1483 1717.359 3.086 1714.273 1698.728 +03h 31m 11.17s 52d47m47.6016s +43d 54m 10.8s 43d54m10.8072s
1 12.212516 GKPer 30842 52.803476 43.901977 257.760120 2010-03-08T19:18:00 2010-03-08T23:09:41 24 00030842024 2031 1978.567 12.484 1966.083 1958.950 +03h 31m 12.83s 52d48m12.5136s +43d 54m 7.1s 43d54m07.1172s
2 15.595530 GKPeroffset1 31653 52.805844 43.905432 239.081804 2010-03-29T21:23:00 2010-03-29T23:53:15 44 00031653044 600 88.973 88.973 0.000 85.682 +03h 31m 13.40s 52d48m21.0384s +43d 54m 19.6s 43d54m19.5552s
3 17.510511 GKPeroffset1 31653 52.793651 43.905866 239.071668 2010-03-12T11:49:59 2010-03-12T12:44:19 11 00031653011 301 39.563 1.963 37.600 39.575 +03h 31m 10.48s 52d47m37.1436s +43d 54m 21.1s 43d54m21.1176s
4 21.057130 GKPer 30842 52.792533 43.902073 270.123645 2007-01-30T01:37:30 2007-01-30T07:19:01 7 00030842007 1054 986.560 19.631 966.929 986.906 +03h 31m 10.21s 52d47m33.1188s +43d 54m 7.5s 43d54m07.4628s
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
140 269.556139 GKPer 30842 52.724707 43.955901 220.787476 2015-04-07T01:17:59 2015-04-07T13:43:55 73 00030842073 1271 1264.049 1264.049 0.000 1256.760 +03h 30m 53.93s 52d43m28.9452s +43d 57m 21.2s 43d57m21.2436s
141 281.324633 GKPeroffset1 31653 52.697974 43.930776 238.993264 2010-03-25T13:09:00 2010-03-25T15:44:59 36 00031653036 600 103.897 103.897 0.000 97.012 +03h 30m 47.51s 52d41m52.7064s +43d 55m 50.8s 43d55m50.7936s
142 287.191916 GKPer 30842 52.692720 43.923969 245.243652 2015-03-26T19:32:59 2015-03-26T21:07:46 62 00030842062 1227 1220.134 4.095 1216.039 1219.336 +03h 30m 46.25s 52d41m33.792s +43d 55m 26.3s 43d55m26.2884s
143 292.861073 GKPer 30842 52.699655 43.941577 249.143502 2015-03-16T13:42:58 2015-03-16T14:36:55 42 00030842042 970 958.988 6.226 952.762 961.740 +03h 30m 47.92s 52d41m58.758s +43d 56m 29.7s 43d56m29.6772s
144 299.472390 GKPer 30842 52.792423 43.987303 241.157935 2015-04-02T03:08:59 2015-04-02T04:02:27 68 00030842068 1058 1054.447 1054.447 0.000 1050.411 +03h 31m 10.18s 52d47m32.7228s +43d 59m 14.3s 43d59m14.2908s

145 rows × 19 columns

You can explore this at your leisure, but let me highlight one point regarding coordinates. In the databases we query, coordinates are stored in decimal degrees (J2000), but this may not be how you want them. So, when you perform a query that gets coordinates, the query module will do a bit of extra work. It identifies all of the coordinate columns and creates sexagesimal versions of the coordinates to (in the format of strings). To identify these, "_s" is appended to the column name (so in the above, "ra" and "decl" were part of the database, and "ra_s", "decl_s" have been added. Also, if you have the astropy module installed then the coordinates will be converted into astropy.coordinate.Angle objects and identied by "_apy" ("ra_apy" and "decl_apy" in the above.

When we executed the query above, we were told how the name supplied had been resolved; but only because we set silent=False, and not in a way that we could have readily captured in our script. Despair not, the details of the name resolution are also in class variables:

q.resolvedInfo
"Resolved 'GK Per' as (52.80004119305, 43.90429720867) via SIMBAD"

Or indeed:

print(q.resolvedRA)
print(q.resolvedDec)
52.80004119305
43.90429720867

We're going to go back and look at addConeSearch() a bit more and explore some of its syntax, but if we just ran q.addConeSearch() again now, we'd get an error telling us our query was locked. This is because when a query is submitted it is locked to prevent ending up in a confused state (a trivial example; imagine you ran the query above and then ran q.addConeSearch(name='FO Aqr') but due to an error, didn't submit it; it would be easy to mistakenly think that q.results was related to the FO Aqr search. It isn't).

So, if we want to do another query we either need to make a new ObsQuery object, or reset the one we have. Let's do the latter:

q.reset()
Resetting query details

Now we can make any changes we like. Before I move on I'll note that q.reset() has some options, you don't have to reset literally everything, but you can read about those via the help command if you want.

So, now we can do another cone search if we want. Before we do, let's look at the arguments that this function takes. You could do this via help (q.addConeSearch) but I'll be nice and elaborate here.

A cone search needs to know two things:

  1. The centre of the cone
  2. The radius of the cone.

The latter is easy, it is managed by two arguments:

The centre of the cone can be specified in a few ways, via these arguments:

You should only provide one of these arguments (OK, two if ra and dec) or you will get an error. name was used above. position is a free-form string and we have tried to accept almost any sane way in which you may enter coordinates (provided they are in J2000). So, all of the examples below will work:

q.addConeSearch(ra=123.456, dec=-43.221, radius=1, units='deg')
q.addConeSearch(position='12 13 15, -15 16 17', radius=12, units='arcmin')

from astropy.coordinates import Angle
ra = Angle('12h 13m 14s')
dec = Angle('-13d 14m 15s')
q.addConeSearch(ra=ra, dec=dec, radius=300, units='arcsec')

If you provided a name or position, it will be resolved when the query is submitted, so you can check the details of the resolution using the q.resolvedRA etc. variables already introduced. If you supplied ra and dec you can actually read these back; for example if you don't trust astropy as used above, we can check that the correct decimal values were extracted:

print(q.coneRA)
print(q.coneDec)
183.3083333333333
-13.237499999999999

Note that these variables are read-only so don't try to edit them. If you made a mistake you can use q.editConeSearch(), which takes exactly the same arguments as addConeSearch().

We can also change our minds completely and remove the cone search settings from our query:

q.removeConeSearch()

That's all there is to setting up the cone search aspect of the query.

Selecting which columns to retrieve

When we ran our demo query above, we got a lot of columns containing data, but what if we didn't want them all? Or if we wanted one not included? The default set of columns returned is not exhaustive. Well, of course, you can change what set of columns you get as we'll now discuss. First of all, it would be nice to know what we are going to get by default. These are stored in the defaultCols variable:

q = uq.ObsQuery(silent=False)
q.defaultCols
Resetting query details
Need to get the metadata.

['name',
 'target_id',
 'ra',
 'decl',
 'roll_angle',
 'start_time',
 'stop_time',
 'obs_segment',
 'obsid',
 'bat_exposure',
 'xrt_exposure',
 'xrt_expo_wt',
 'xrt_expo_pc',
 'uvot_exposure']

As you can see, this is just a list, and it's a list of column names. Which is nice and all, but what actually are these columns, and what extra ones are available?

Because we have silent=False you can see from the above that in order to identify the default columns, the Python module grabbed some metadata. It is this metadata which tells us all about the table. Let's take a look at it:

q.metadata
ColName Class Description Type LongDescription IsObsCol IsTargetCol
0 name BASIC Designation of the NFI Pointed Source TXT This is the name of the pointed target. For GR... 0 0
1 orig_target_id MISC Trigger Number as Originally Assigned INT This is a numerical value assigned automatical... 0 0
2 target_id BASIC Unique Trigger Number with Any Degeneracy Removed INT This is a unique numerical value assigned to e... 0 1
3 ra BASIC Right Ascension (Pointing Position) COORDH Right Ascension of the pointing position. Note... 0 0
4 decl BASIC Declination (Pointing Position) COORDD Declination of the pointing position. Note tha... 0 0
5 roll_angle BASIC Roll Angle (degree) FLOAT Roll angle of the observation given in degrees. 0 0
6 start_time BASIC Start Time of the Observation TXT Start time of the observation. Note that the d... 0 0
7 stop_time BASIC Stop Time of the Observation TXT Stop time of the observation. For an Automatic... 0 0
8 orig_obs_segment MISC Observation Segment as Originally Assigned INT The Swift observation strategy is similar to a... 0 0
9 obs_segment BASIC True Observation Segment (Corrected Value) INT Observation Segment. The Swift observation str... 0 0
10 orig_obsid MISC Observation Number as Originally Assigned (Ori... TXT This parameter contains a numeric value that s... 0 0
11 obsid BASIC Unique Observation Number (Target_ID + Obs_Seg... TXT This parameter contains a numeric value that u... 1 0
12 bat_exposure BASIC BAT Effective Exposure Event and Survey modes with... FLOAT The BAT exposure in seconds on source. The BAT... 0 0
13 xrt_exposure BASIC XRT Effective Exposure on Source All XRT Modes FLOAT The XRT exposure in seconds on source. The XRT... 0 0
14 xrt_expo_wt BASIC XRT Effective Exposure on Source Windowed Timing ... FLOAT The XRT exposure on source when the Windowed T... 0 0
15 xrt_expo_pc BASIC XRT Effective Exposure on Source Photon Counting X... FLOAT The XRT exposure on source when the Photon Cou... 0 0
16 uvot_exposure BASIC UVOT Effective Exposure on Source All UVOT Filters ... FLOAT The UVOT exposure in seconds on source. The UV... 0 0
17 xrt_expo_lr XRT Effective Exposure on Source Low Rate Photodio... FLOAT The XRT exposure on source when the Low-Rate P... 0 0
18 xrt_expo_pu XRT Effective Exposure on Source Piled-up Photodio... FLOAT The XRT exposure on source when the Piled-Up P... 0 0
19 xrt_expo_im XRT Effective Exposure on Source Image Modes FLOAT The XRT exposure on source when the IMAGE (IM)... 0 0
20 uvot_expo_uu UVOT Effective Exposure on Source U UVOT Filter FLOAT The UVOT exposure on source when the U filter ... 0 0
21 uvot_expo_bb UVOT Effective Exposure on Source B UVOT Filter FLOAT The UVOT exposure on source when the B filter ... 0 0
22 uvot_expo_vv UVOT Effective Exposure on Source V UVOT Filter FLOAT The UVOT exposure on source when the V filter ... 0 0
23 uvot_expo_w1 UVOT Effective Exposure on Source UVW1 UVOT Filter FLOAT The UVOT exposure on source when the UVW1 filt... 0 0
24 uvot_expo_w2 UVOT Effective Exposure on Source UVW2 UVOT Filter FLOAT The UVOT exposure on source when the UVW2 filt... 0 0
25 uvot_expo_m2 UVOT Effective Exposure on Source UVM2 UVOT Filter FLOAT The UVOT exposure on source when the UVM2 filt... 0 0
26 uvot_expo_wh UVOT Effective Exposure on Source WHITE UVOT Filter FLOAT The UVOT exposure on source when the White fil... 0 0
27 uvot_expo_gu UVOT Effective Exposure on Source UGRISM UVOT Filter FLOAT The UVOT exposure on source when the UGRISM fi... 0 0
28 uvot_expo_gv UVOT Effective Exposure on Source VGRISM UVOT Filter FLOAT The UVOT exposure on source when the VGRISM fi... 0 0
29 uvot_expo_mg UVOT Effective Exposure on Source Magnifier UVOT Fi... FLOAT The UVOT exposure on source when the Magnifier... 0 0
30 uvot_expo_bl UVOT Effective Exposure Blocked Filter FLOAT None 0 0
31 bat_expo_ev BAT Effective Exposure Event mode FLOAT The BAT exposure on source when the EVENT mode... 0 0
32 bat_expo_sv BAT Effective Exposure Survey mode FLOAT BAT exposure on source when the SURVEY mode wa... 0 0
33 bat_expo_rt BAT Effective Exposure Rate mode FLOAT BAT exposure on source when the RATE mode was ... 0 0
34 bat_expo_mt BAT Effective Exposure Masktag mode FLOAT Cumulative BAT exposure of all sources tagged ... 0 0
35 bat_expo_pl BAT Effective Exposure pulsar mode FLOAT BAT exposure on source when the PULSAR mode wa... 0 0
36 bat_no_masktag BAT Number of sources with mask tag rate within th... INT Number of sources for which the MASKTAG rate m... 0 0
37 archive_date MISC Public date TXT The archive_date is the date when the data are... 0 0
38 soft_version MISC Software version used in the pipeline TXT None 0 0
39 processing_date MISC Date of Processing TXT This records the date when the data were proce... 0 0
40 processing_version MISC Version of Processing Scrip TXT This records the version of the processing scr... 0 0
41 num_processed MISC Number of Times a Sequence Has Been Processed INT This records the number of times a data set ha... 0 0
42 cycle MISC Proposal cycle number INT The proposal cycle number of the observation. 0 0
43 pi MISC PI Last Name TXT Principal Investigator. If the Swift Announcem... 0 0
44 att_flag MISC Attitude flag TXT Attitude flag. 0 0

Table metadata is made available in the form of a pandas DataFrame. The exact columns in this do differ slightly for the different tables (and modules; 'LongDescription' is only present for the ObsQuery class), and not all of them are relevant for you - some are needed internally by the Python module, and some are needed by the web interfaces to the databases. The columns you really care about are "ColName" and "Description" which should provide enough details for you to choose whether the default columns are enough, or if you want to edit them.

So, knowing what columns exist, we can now decide which ones we want to obtain, if we didn't just want the default. We add columns using the addCol() function. This takes either * (to get everything), a column name, or a list/tuple of column names. So:

q.addCol('name')
q.colsToGet
['name']

As shown above, the colsToGet variable (which you cannot edit directly) tells you what columns are selected, and you may have noticed that now we've explicitly said what we want, all the default columns are not there. We can easily add them back in though:

q.addCol(q.defaultCols)
q.colsToGet
Cannot add column name; it is already selected.

['name',
 'target_id',
 'ra',
 'decl',
 'roll_angle',
 'start_time',
 'stop_time',
 'obs_segment',
 'obsid',
 'bat_exposure',
 'xrt_exposure',
 'xrt_expo_wt',
 'xrt_expo_pc',
 'uvot_exposure']

You note that we got a warning because we tried to add an existing column in, but it was just a warning and did not stop the other columns being added. We can also remove columns with removeCol, or removeAllCols. So here are some examples:

q.removeCol('name')
q.colsToGet
['target_id',
 'ra',
 'decl',
 'roll_angle',
 'start_time',
 'stop_time',
 'obs_segment',
 'obsid',
 'bat_exposure',
 'xrt_exposure',
 'xrt_expo_wt',
 'xrt_expo_pc',
 'uvot_exposure']
q.removeCol(('obsid', 'stop_time'))
q.colsToGet
['target_id',
 'ra',
 'decl',
 'roll_angle',
 'start_time',
 'obs_segment',
 'bat_exposure',
 'xrt_exposure',
 'xrt_expo_wt',
 'xrt_expo_pc',
 'uvot_exposure']
q.addCol(['cycle', 'soft_version']+q.defaultCols)
Cannot add column target_id; it is already selected.
Cannot add column ra; it is already selected.
Cannot add column decl; it is already selected.
Cannot add column roll_angle; it is already selected.
Cannot add column start_time; it is already selected.
Cannot add column obs_segment; it is already selected.
Cannot add column bat_exposure; it is already selected.
Cannot add column xrt_exposure; it is already selected.
Cannot add column xrt_expo_wt; it is already selected.
Cannot add column xrt_expo_pc; it is already selected.
Cannot add column uvot_exposure; it is already selected.

The latter was a slightly gratuitous demonstration of a way that you can add the default columns, and some others, all in one go. We can also remove the whole lot in one go:

q.removeAllCols()
q.colsToGet

If we were to submit this now we would not get an empty result set, but rather the default columns, which are always request if colsToGet is empty.

If you've been reading everything very carefully, you may be wondering why the results from our demonstration cone search contained the column _r, which has not appeared anywhere in the metadata or our requests. _r is a special column only created for cone searches, and it contains the angular distance of each row from the centre of the search. The units are whatever was given as the units argument to addConeSearch() (arcsec by default).


'Advanced' searches

'Advanced' searches are those where we select data based on filters applied to specific columns. This is actually fairly simple, we just define a series of filters to apply to the query and then submit it. These can be in addition to, or instead of the cone search. There are a few things to note before we start:

  1. Filters are combined with a logical AND; that is if you define multiple filters, only rows matching all of them will be returned.
  2. Each filter applies to a single column but can have two clauses, combined with AND or OR.

This will hopefully all make sense as we go on.

Filters

We add filters using the imaginatively-named addFilter() function, which takes a single argument, a filter definition. A filter definition can either be a dict or a list and has the following components:

I will unpack these in more detail but an example is probably a better helper so, here are two filters created using the two approaches:

filter1 = ('xrt_exposure', '>', 1000, 'OR', '<', 200)

filter2 = {
    "colName": "ra", 
    "filter": ">", 
    "val": 123,
    "combiner": "and" ,
    "filter2": "<",
    "val2": 200
}

If these filters were converted to SQL they would be written as

And if we submitted this query they would combine as:

(xrt_exposure>1000 OR xrt_exposure<200) AND (ra>123 AND ra<200)

If we wanted to create a query using these filters we would do:

q = uq.ObsQuery(silent=False)
q.addFilter(filter1)
q.addFilter(filter2)
Resetting query details
Need to get the metadata.

A filter doesn't have to have all of the elements we used above. You may not want the second part of the filter, so everything from combiner onwards can simply be omitted. Also, some of the filters do not require arguments, so in that case, val (or val2) can be left out. Conversely, the 'BETWEEN' filter requires two arguments and so val must take a 2-element tuple/list.

Some more examples:

q.removeAllFilters()

q.addFilter ( ('xrt_exposure', '<', 2000))

q.addFilter ( ('ra', 'BETWEEN', [100,200]))

q.addFilter ({
    'colName': 'target_id',
    'filter': 'IS NULL',
    'combiner': 'or',
    'filter2': '<',
    'val2': 10000    
})

Here I've introduced the removeAllFilters() function (you can probably guess what it does) and a few more examples of adding filters, including some where we don't need all elements of the filter definition.

The following values are permitted for the 'filter' and 'filter2' keys:

All of these require a single value, except for 'BETWEEN', which requires 2, and 'IS NULL' and 'IS NOT NULL' which take no values.

Having added filters we can check them:

q.showFilters()
0:  xrt_exposure < 2000
1:  ra BETWEEN [100, 200]
2:  target_id IS NULL OR < 10000

The outputs as you can see are strings.

We can also remove specific filters by their index. Please bear in mind indices will change when you remove a filter!

q.removeFilter(1)
0:  xrt_exposure < 2000
1:  target_id IS NULL OR < 10000

Because silent=False the above function printed out the revised set of filters, if it was True you'd have to do this yourself with another showFilters() call.

Right, enough messing around, let's do an actual query. I'm going to do both a cone and advanced search together here, just to prove you can (and to limit how many rows we get), but of course, you don't have to.

q=uq.ObsQuery(silent=False)
q.addConeSearch(name='GK Per', radius=12, units='arcmin')
q.addFilter( ('xrt_exposure', '>', 3000))
q.isValid()
Resetting query details
Need to get the metadata.

True

Here, we have created a query, requested a cone search of 12' radius, centred on "GK Per", and we have asked to only get those rows where the "xrt_exposure" field is above 3000. I also made use of a function, isValid(). This just checks that the syntax is OK and we haven't done anything silly. Let's submit the query:

q.submit()
Calling DB look-up for rows 0 -- 1000
Resolved 'GK Per' as (52.80004119305, 43.90429720867) via SIMBAD
Received 21 rows.

Again, because we were not silent we got some information printed, but this is also available in variables. I have already discussed accessing details of the name resolution, but how could I find out that 21 rows were returned without having to read from the screen? Like this:

q.numRows
21

Or indeed:

len(q.results)
21

if you prefer. They should be the same. And let's just prove that our filters worked:

q.results
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
0 0.421999 GKPer 30842 52.793185 43.899291 280.915675 2007-01-17T03:10:01 2007-01-17T13:41:49 5 00030842005 6053 6078.284 60.784 6017.500 5862.453 +03h 31m 10.36s 52d47m35.466s +43d 53m 57.4s 43d53m57.4476s
1 0.483022 GKPer 30842 52.811212 43.904125 284.011915 2007-01-09T08:32:01 2007-01-09T12:38:45 4 00030842004 4896 4846.696 25.133 4821.563 4844.257 +03h 31m 14.69s 52d48m40.3632s +43d 54m 14.9s 43d54m14.85s
2 0.515095 GKPer 30842 52.788651 43.906818 266.451771 2007-02-08T03:47:01 2007-02-08T22:21:13 9 00030842009 5374 5278.844 28.914 5249.930 5271.033 +03h 31m 9.28s 52d47m19.1436s +43d 54m 24.5s 43d54m24.5448s
3 0.916885 GKPer 30842 52.779428 43.907897 292.140501 2007-01-02T12:43:01 2007-01-02T16:47:21 3 00030842003 4788 4733.461 41.147 4692.314 4735.406 +03h 31m 7.06s 52d46m45.9408s +43d 54m 28.4s 43d54m28.4292s
4 0.962148 GKPer 30842 52.778149 43.901410 269.888920 2007-02-04T14:50:01 2007-02-04T20:23:10 8 00030842008 4046 3979.563 43.122 3936.441 3978.030 +03h 31m 6.76s 52d46m41.3364s +43d 54m 5.1s 43d54m05.076s
5 1.161197 GKPer 30842 52.773182 43.904518 276.237384 2007-01-23T02:08:01 2007-01-23T07:50:49 6 00030842006 5872 5806.444 27.112 5779.332 5804.662 +03h 31m 5.56s 52d46m23.4552s +43d 54m 16.3s 43d54m16.2648s
6 1.498005 GK_Per 81445 52.829137 43.890741 63.483317 2015-09-09T09:14:58 2015-09-09T18:04:10 2 00081445002 4458 4419.383 0.040 4419.343 4350.644 +03h 31m 18.99s 52d49m44.8932s +43d 53m 26.7s 43d53m26.6676s
7 1.589266 GKPer 30842 52.763626 43.900668 252.039706 2007-02-12T02:16:00 2007-02-12T14:23:37 11 00030842011 3159 3023.056 59.527 2963.529 3023.295 +03h 31m 3.27s 52d45m49.0536s +43d 54m 2.4s 43d54m02.4048s
8 1.642341 GKPer 30842 52.770293 43.921326 308.921791 2006-12-20T16:12:19 2006-12-20T18:34:48 1 00030842001 4022 3980.145 33.504 3946.641 3988.765 +03h 31m 4.87s 52d46m13.0548s +43d 55m 16.8s 43d55m16.7736s
9 1.798257 GKPer 30842 52.837858 43.916788 300.429716 2006-12-26T02:26:01 2006-12-26T08:07:50 2 00030842002 4607 4540.904 30.284 4510.620 4538.745 +03h 31m 21.09s 52d50m16.2888s +43d 55m 0.4s 43d55m00.4368s
10 2.034272 GKPeroffset1 31653 52.802362 43.870434 238.001562 2010-04-01T02:27:00 2010-04-01T19:52:48 47 00031653047 3481 5341.812 5341.812 0.000 5288.652 +03h 31m 12.57s 52d48m08.5032s +43d 52m 13.6s 43d52m13.5624s
11 2.234021 GKPeroffset1 31653 52.800838 43.867068 239.993197 2010-04-05T02:45:59 2010-04-05T20:13:48 49 00031653049 4562 5341.801 5341.801 0.000 5286.456 +03h 31m 12.20s 52d48m03.0168s +43d 52m 1.4s 43d52m01.4448s
12 2.501833 GKPer 45767 52.743700 43.913840 249.208788 2013-03-20T01:02:59 2013-03-21T01:20:22 5 00045767005 4143 4121.740 63.538 4058.202 4087.774 +03h 30m 58.49s 52d44m37.32s +43d 54m 49.8s 43d54m49.824s
13 2.570384 GKPer 30842 52.740645 43.906271 262.071536 2007-02-16T02:39:01 2007-02-16T08:20:54 12 00030842012 6303 6238.041 82.605 6155.436 6237.197 +03h 30m 57.75s 52d44m26.322s +43d 54m 22.6s 43d54m22.5756s
14 2.606862 GKPer 30842 52.745736 43.923199 253.806039 2007-03-13T00:29:01 2007-03-13T11:01:02 19 00030842019 6106 5969.147 78.192 5890.955 5981.222 +03h 30m 58.98s 52d44m44.6496s +43d 55m 23.5s 43d55m23.5164s
15 2.802018 GKPer 30842 52.745146 43.929141 260.414911 2007-02-26T00:25:01 2007-02-26T06:08:24 15 00030842015 6488 6403.276 32.808 6370.468 6418.423 +03h 30m 58.84s 52d44m42.5256s +43d 55m 44.9s 43d55m44.9076s
16 2.875683 GKPer 30842 52.734551 43.912719 261.255083 2007-02-23T00:06:01 2007-02-23T02:36:36 14 00030842014 3277 3244.444 5.008 3239.436 3243.491 +03h 30m 56.29s 52d44m04.3836s +43d 54m 45.8s 43d54m45.7884s
17 2.940585 GKPer 30842 52.732920 43.912266 255.973292 2007-03-06T04:39:01 2007-03-06T14:14:01 17 00030842017 7951 7846.457 51.297 7795.160 7848.622 +03h 30m 55.90s 52d43m58.512s +43d 54m 44.2s 43d54m44.1576s
18 3.344602 GKPer 30842 52.726982 43.922663 254.061770 2007-03-09T00:16:01 2007-03-09T23:26:23 18 00030842018 4570 4311.634 111.419 4200.215 4228.598 +03h 30m 54.48s 52d43m37.1352s +43d 55m 21.6s 43d55m21.5868s
19 3.541167 GKPer 30842 52.718735 43.897146 242.945235 2007-03-02T12:04:01 2007-03-02T22:36:04 16 00030842016 3410 3261.669 118.117 3143.552 3288.439 +03h 30m 52.50s 52d43m07.446s +43d 53m 49.7s 43d53m49.7256s
20 5.582058 GKPer 30842 52.878237 43.978359 50.625546 2007-09-27T14:04:00 2007-09-28T01:29:45 20 00030842020 3089 3010.216 0.000 3010.216 0.000 +03h 31m 30.78s 52d52m41.6532s +43d 58m 42.1s 43d58m42.0924s

Extra query settings

There are one or two further things to discuss: sorting the results, and controlling how many rows we get. Both of these can be done after the query is complete using pandas functions, or they can be done at query time. Let's start with sorting.

Sorting results

By default, if you did a cone search the results are ordered by increasing distance from the cone centre. If you didn't do a cone search, they are ordered by however they come out of the database. To control this sorting, we use two variables in our class sortCol and sortDir. The former is the name of a column in the table (which we can get via q.metadata, as above), the latter is either "ASC" or "DESC", (for ascending or descending).

Let's have a quick demo, and while I'm here I'll show you that you can unlock a query, rather than fully resetting it, if you just want to change something and resubmit:

q.unlock()
q.sortCol='xrt_exposure'
q.sortDir='DESC'
q.submit()
q.results
Calling DB look-up for rows 0 -- 1000
Resolved 'GK Per' as (52.80004119305, 43.90429720867) via SIMBAD
Received 21 rows.
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
0 2.940585 GKPer 30842 52.732920 43.912266 255.973292 2007-03-06T04:39:01 2007-03-06T14:14:01 17 00030842017 7951 7846.457 51.297 7795.160 7848.622 +03h 30m 55.90s 52d43m58.512s +43d 54m 44.2s 43d54m44.1576s
1 2.802018 GKPer 30842 52.745146 43.929141 260.414911 2007-02-26T00:25:01 2007-02-26T06:08:24 15 00030842015 6488 6403.276 32.808 6370.468 6418.423 +03h 30m 58.84s 52d44m42.5256s +43d 55m 44.9s 43d55m44.9076s
2 2.570384 GKPer 30842 52.740645 43.906271 262.071536 2007-02-16T02:39:01 2007-02-16T08:20:54 12 00030842012 6303 6238.041 82.605 6155.436 6237.197 +03h 30m 57.75s 52d44m26.322s +43d 54m 22.6s 43d54m22.5756s
3 0.421999 GKPer 30842 52.793185 43.899291 280.915675 2007-01-17T03:10:01 2007-01-17T13:41:49 5 00030842005 6053 6078.284 60.784 6017.500 5862.453 +03h 31m 10.36s 52d47m35.466s +43d 53m 57.4s 43d53m57.4476s
4 2.606862 GKPer 30842 52.745736 43.923199 253.806039 2007-03-13T00:29:01 2007-03-13T11:01:02 19 00030842019 6106 5969.147 78.192 5890.955 5981.222 +03h 30m 58.98s 52d44m44.6496s +43d 55m 23.5s 43d55m23.5164s
5 1.161197 GKPer 30842 52.773182 43.904518 276.237384 2007-01-23T02:08:01 2007-01-23T07:50:49 6 00030842006 5872 5806.444 27.112 5779.332 5804.662 +03h 31m 5.56s 52d46m23.4552s +43d 54m 16.3s 43d54m16.2648s
6 2.034272 GKPeroffset1 31653 52.802362 43.870434 238.001562 2010-04-01T02:27:00 2010-04-01T19:52:48 47 00031653047 3481 5341.812 5341.812 0.000 5288.652 +03h 31m 12.57s 52d48m08.5032s +43d 52m 13.6s 43d52m13.5624s
7 2.234021 GKPeroffset1 31653 52.800838 43.867068 239.993197 2010-04-05T02:45:59 2010-04-05T20:13:48 49 00031653049 4562 5341.801 5341.801 0.000 5286.456 +03h 31m 12.20s 52d48m03.0168s +43d 52m 1.4s 43d52m01.4448s
8 0.515095 GKPer 30842 52.788651 43.906818 266.451771 2007-02-08T03:47:01 2007-02-08T22:21:13 9 00030842009 5374 5278.844 28.914 5249.930 5271.033 +03h 31m 9.28s 52d47m19.1436s +43d 54m 24.5s 43d54m24.5448s
9 0.483022 GKPer 30842 52.811212 43.904125 284.011915 2007-01-09T08:32:01 2007-01-09T12:38:45 4 00030842004 4896 4846.696 25.133 4821.563 4844.257 +03h 31m 14.69s 52d48m40.3632s +43d 54m 14.9s 43d54m14.85s
10 0.916885 GKPer 30842 52.779428 43.907897 292.140501 2007-01-02T12:43:01 2007-01-02T16:47:21 3 00030842003 4788 4733.461 41.147 4692.314 4735.406 +03h 31m 7.06s 52d46m45.9408s +43d 54m 28.4s 43d54m28.4292s
11 1.798257 GKPer 30842 52.837858 43.916788 300.429716 2006-12-26T02:26:01 2006-12-26T08:07:50 2 00030842002 4607 4540.904 30.284 4510.620 4538.745 +03h 31m 21.09s 52d50m16.2888s +43d 55m 0.4s 43d55m00.4368s
12 1.498005 GK_Per 81445 52.829137 43.890741 63.483317 2015-09-09T09:14:58 2015-09-09T18:04:10 2 00081445002 4458 4419.383 0.040 4419.343 4350.644 +03h 31m 18.99s 52d49m44.8932s +43d 53m 26.7s 43d53m26.6676s
13 3.344602 GKPer 30842 52.726982 43.922663 254.061770 2007-03-09T00:16:01 2007-03-09T23:26:23 18 00030842018 4570 4311.634 111.419 4200.215 4228.598 +03h 30m 54.48s 52d43m37.1352s +43d 55m 21.6s 43d55m21.5868s
14 2.501833 GKPer 45767 52.743700 43.913840 249.208788 2013-03-20T01:02:59 2013-03-21T01:20:22 5 00045767005 4143 4121.740 63.538 4058.202 4087.774 +03h 30m 58.49s 52d44m37.32s +43d 54m 49.8s 43d54m49.824s
15 1.642341 GKPer 30842 52.770293 43.921326 308.921791 2006-12-20T16:12:19 2006-12-20T18:34:48 1 00030842001 4022 3980.145 33.504 3946.641 3988.765 +03h 31m 4.87s 52d46m13.0548s +43d 55m 16.8s 43d55m16.7736s
16 0.962148 GKPer 30842 52.778149 43.901410 269.888920 2007-02-04T14:50:01 2007-02-04T20:23:10 8 00030842008 4046 3979.563 43.122 3936.441 3978.030 +03h 31m 6.76s 52d46m41.3364s +43d 54m 5.1s 43d54m05.076s
17 3.541167 GKPer 30842 52.718735 43.897146 242.945235 2007-03-02T12:04:01 2007-03-02T22:36:04 16 00030842016 3410 3261.669 118.117 3143.552 3288.439 +03h 30m 52.50s 52d43m07.446s +43d 53m 49.7s 43d53m49.7256s
18 2.875683 GKPer 30842 52.734551 43.912719 261.255083 2007-02-23T00:06:01 2007-02-23T02:36:36 14 00030842014 3277 3244.444 5.008 3239.436 3243.491 +03h 30m 56.29s 52d44m04.3836s +43d 54m 45.8s 43d54m45.7884s
19 1.589266 GKPer 30842 52.763626 43.900668 252.039706 2007-02-12T02:16:00 2007-02-12T14:23:37 11 00030842011 3159 3023.056 59.527 2963.529 3023.295 +03h 31m 3.27s 52d45m49.0536s +43d 54m 2.4s 43d54m02.4048s
20 5.582058 GKPer 30842 52.878237 43.978359 50.625546 2007-09-27T14:04:00 2007-09-28T01:29:45 20 00030842020 3089 3010.216 0.000 3010.216 0.000 +03h 31m 30.78s 52d52m41.6532s +43d 58m 42.1s 43d58m42.0924s

As you can see, this time, the results are ordered by the "xrt_exposure" column, in descending order. Incidentally, you don't have to actually retrieve the column you sort on, if you don't want to!

How many rows to get

The default behaviour of this module is to get all rows in the database that match your query. This can be a lot, and maybe you don't want them all. You can limit how many are returned using the maxRows variable:

q.unlock()
q.maxRows=3
q.submit()
Calling DB look-up for rows 0 -- 3
Resolved 'GK Per' as (52.80004119305, 43.90429720867) via SIMBAD
Received 3 rows.

This returned us the top three rows matching our query - and note that as we have (just a moment ago) said that our results should be ordered by descending XRT exposure, we should have got the three observations with the longest exposures.

q.results
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
0 2.940585 GKPer 30842 52.732920 43.912266 255.973292 2007-03-06T04:39:01 2007-03-06T14:14:01 17 00030842017 7951 7846.457 51.297 7795.160 7848.622 +03h 30m 55.90s 52d43m58.512s +43d 54m 44.2s 43d54m44.1576s
1 2.802018 GKPer 30842 52.745146 43.929141 260.414911 2007-02-26T00:25:01 2007-02-26T06:08:24 15 00030842015 6488 6403.276 32.808 6370.468 6418.423 +03h 30m 58.84s 52d44m42.5256s +43d 55m 44.9s 43d55m44.9076s
2 2.570384 GKPer 30842 52.740645 43.906271 262.071536 2007-02-16T02:39:01 2007-02-16T08:20:54 12 00030842012 6303 6238.041 82.605 6155.436 6237.197 +03h 30m 57.75s 52d44m26.322s +43d 54m 22.6s 43d54m22.5756s

We did!

I said a moment ago that by default all matching rows are returned, and this is true. Some of you may be wondering why, therefore, the output above repeatedly says: "Calling DB look-up for rows 0 -- 1000" (if you are not wondering this, skip to the next section). Was I lying about getting all rows?

No, I wasn't, and in general what I'm about to say won't matter, but it's here to satisfy your curiosity.

There is a limit to the amount of server resources a single query can consume. In practice this means that if you request some enormous query with lots of rows, the query will be terminated because it uses more memory than is permitted and you will get some unhelpful error (probably an HTTP 500 error). To avoid this, the Python module will never ask for more than 1,000 rows at a time. But this doesn't mean that you only get 1,000 rows; it just means that the Python back end will get your results in chunks, requesting the first 1,000 rows and then (if necessary) the next 1,000 etc., giving (non-silent) output such as:

Calling DB look-up for rows 0 -- 1000 Calling DB look-up for rows 1001 -- 2000 Calling DB look-up for rows 2001 -- 3000

The results of these calls will be stitched together for you and this entire process would be completely invisible if we hadn't said silent=False.


Retrieving products from a query

Having identified observations using a query, you may wish to download them. You can do this by calling the downloadObsData() function. This is literally just a wrapper the function of the same name in the data module and as already warned, I'm not going to redocument that here. Arguments are passed through to data.downloadObsData() as **kwargs, with just three exceptions. These exceptions apply to almost every data product function provided throughout the query module, so I will refer back to this section a few times on subsequent page. These exceptions are:

This last point we will return to in a moment

First, let's do a simple demonstration of this. I will deliberately execute a query that doesn't get too many rows.

q = uq.ObsQuery(silent=False)
q.addConeSearch(name='GRB 210205A', radius=300)
q.submit()
q.results
Resetting query details
Need to get the metadata to check the query is valid.
Calling DB look-up for rows 0 -- 1000
Resolved 'GRB 210205A' as (347.219333, 56.296528) via SIMBAD
Received 2 rows.
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
0 84.722734 Burst (347.221, 56.294) 1030629 347.229229 56.319413 197.285029 2021-02-05T13:54:35 2021-02-05T22:16:56 1 01030629001 7906.000 7866.813 0.00 7866.813 7786.218 +23h 08m 55.01s 347d13m45.2244s +56d 19m 9.9s 56d19m09.8868s
1 158.399615 Burst (347.221, 56.294) 1030629 347.274472 56.328161 207.713073 2021-02-05T10:53:19 2021-02-05T12:40:56 0 01030629000 4833.061 1725.085 5.02 1714.987 1686.866 +23h 09m 5.87s 347d16m28.0992s +56d 19m 41.4s 56d19m41.3796s

As you can see, this gave us two rows, and we can save the observations simply enough.

q.downloadObsData(destDir='/tmp/APIDemo_download1',
                 instruments=('BAT', 'XRT'),
                 getTDRSS=True)
Making directory /tmp/APIDemo_download1
Downloading 2 datasets
Making directory /tmp/APIDemo_download1/01030629001
Making directory /tmp/APIDemo_download1/01030629001/bat/
Making directory /tmp/APIDemo_download1/01030629001/bat/hk/
Making directory /tmp/APIDemo_download1/01030629001/bat/masktag/
Making directory /tmp/APIDemo_download1/01030629001/bat/rate/
Making directory /tmp/APIDemo_download1/01030629001/bat/survey/
Making directory /tmp/APIDemo_download1/01030629001/xrt/
Making directory /tmp/APIDemo_download1/01030629001/xrt/event/
Making directory /tmp/APIDemo_download1/01030629001/xrt/hk/
Making directory /tmp/APIDemo_download1/01030629001/auxil/

Downloading 01030629001:   0%|          | 0/46 [00:00<?, ?files/s]

Making directory /tmp/APIDemo_download1/01030629000
Making directory /tmp/APIDemo_download1/01030629000/bat/
Making directory /tmp/APIDemo_download1/01030629000/bat/event/
Making directory /tmp/APIDemo_download1/01030629000/bat/hk/
Making directory /tmp/APIDemo_download1/01030629000/bat/masktag/
Making directory /tmp/APIDemo_download1/01030629000/bat/products/
Making directory /tmp/APIDemo_download1/01030629000/bat/rate/
Making directory /tmp/APIDemo_download1/01030629000/bat/survey/
Making directory /tmp/APIDemo_download1/01030629000/xrt/
Making directory /tmp/APIDemo_download1/01030629000/xrt/event/
Making directory /tmp/APIDemo_download1/01030629000/xrt/hk/
Making directory /tmp/APIDemo_download1/01030629000/auxil/
Making directory /tmp/APIDemo_download1/01030629000/tdrss/

Downloading 01030629000:   0%|          | 0/90 [00:00<?, ?files/s]

The arguments I supplied to downloadObsData() are the standard arguments for data.downloadObsData() which are documented here.

One thing that is worth noting is that, since downloadObsData() uses the observation identifier to work out which data you are asking for, if your query did not retrieve the relevant column, this function would fail. If you are manually selecting columns and want to ensure you have the necessary column you can always look at the metadata (described above). If the table in question contains observation identifiers then the metadata will include "IsObsCol", which will be 1 for the relevant column.

Getting products for only some rows

After performing a query you may realise that you don't want to get data for all of the rows, but only some of them. You could repeat the query, of course, with extra filtering, but this would be a bit wasteful. The subset parameter solves this; it exists for all functions for getting products via the query module, and allows you to define a subset of rows for which products should be retrieved.

The argument itself takes a pandas Series of bools, identifying the rows, which sounds complicated, but actually you just need to give it a pandas filter expression. It is beyond the scope of this documentation to describe those in detail, but the examples below should give you a primer.

First, I'm going to do a cone search around GK Per, ordering by exposure (longest first)

q = uq.ObsQuery(silent=False)
q.addConeSearch(name='GK Per', radius=300)
q.sortCol = 'xrt_exposure'
q.sortDir='DESC'
q.submit()
q.results
Resetting query details
Need to get the metadata.
Calling DB look-up for rows 0 -- 1000
Resolved 'GK Per' as (52.80004119305, 43.90429720867) via SIMBAD
Received 145 rows.
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
0 176.435074 GKPer 30842 52.732920 43.912266 255.973292 2007-03-06T04:39:01 2007-03-06T14:14:01 17 00030842017 7951 7846.457 51.297 7795.160 7848.622 +03h 30m 55.90s 52d43m58.512s +43d 54m 44.2s 43d54m44.1576s
1 168.121066 GKPer 30842 52.745146 43.929141 260.414911 2007-02-26T00:25:01 2007-02-26T06:08:24 15 00030842015 6488 6403.276 32.808 6370.468 6418.423 +03h 30m 58.84s 52d44m42.5256s +43d 55m 44.9s 43d55m44.9076s
2 154.223024 GKPer 30842 52.740645 43.906271 262.071536 2007-02-16T02:39:01 2007-02-16T08:20:54 12 00030842012 6303 6238.041 82.605 6155.436 6237.197 +03h 30m 57.75s 52d44m26.322s +43d 54m 22.6s 43d54m22.5756s
3 25.319939 GKPer 30842 52.793185 43.899291 280.915675 2007-01-17T03:10:01 2007-01-17T13:41:49 5 00030842005 6053 6078.284 60.784 6017.500 5862.453 +03h 31m 10.36s 52d47m35.466s +43d 53m 57.4s 43d53m57.4476s
4 156.411729 GKPer 30842 52.745736 43.923199 253.806039 2007-03-13T00:29:01 2007-03-13T11:01:02 19 00030842019 6106 5969.147 78.192 5890.955 5981.222 +03h 30m 58.98s 52d44m44.6496s +43d 55m 23.5s 43d55m23.5164s
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
140 105.896403 GKPeroffset1 31653 52.809250 43.875640 239.062783 2010-03-22T22:23:00 2010-03-22T23:17:16 32 00031653032 0 42.338 4.109 38.229 42.565 +03h 31m 14.22s 52d48m33.3s +43d 52m 32.3s 43d52m32.304s
141 17.510511 GKPeroffset1 31653 52.793651 43.905866 239.071668 2010-03-12T11:49:59 2010-03-12T12:44:19 11 00031653011 301 39.563 1.963 37.600 39.575 +03h 31m 10.48s 52d47m37.1436s +43d 54m 21.1s 43d54m21.1176s
142 116.995543 GKPer 30842 52.833519 43.926082 255.065851 2010-03-09T00:08:00 2010-03-09T01:01:34 26 00030842026 300 26.660 4.104 22.556 24.239 +03h 31m 20.04s 52d50m00.6684s +43d 55m 33.9s 43d55m33.8952s
143 117.981049 GKPer 30842 52.836633 43.923770 255.063174 2010-03-09T21:01:00 2010-03-09T21:54:33 30 00030842030 300 26.239 13.712 12.527 25.243 +03h 31m 20.79s 52d50m11.8788s +43d 55m 25.6s 43d55m25.572s
144 73.054432 GKPeroffset1 31653 52.792369 43.923823 240.067049 2010-04-05T02:43:00 2010-04-05T19:44:47 48 00031653048 900 14.457 14.457 0.000 11.585 +03h 31m 10.17s 52d47m32.5284s +43d 55m 25.8s 43d55m25.7628s

145 rows × 19 columns

Now, looking at this, I want to get only those rows that have more than 6ks of XRT data. That is, those rows where q.results['xrt_exposure']>6000, and that is my subset definition:

q.downloadObsData(destDir='/tmp/APIDemo_download2',
                  subset=q.results['xrt_exposure']>6000,
                  instruments=('XRT',),
                  getAuxil=False)
Making directory /tmp/APIDemo_download2
Downloading 4 datasets
Making directory /tmp/APIDemo_download2/00030842017
Making directory /tmp/APIDemo_download2/00030842017/xrt/
Making directory /tmp/APIDemo_download2/00030842017/xrt/event/
Making directory /tmp/APIDemo_download2/00030842017/xrt/hk/
Making directory /tmp/APIDemo_download2/00030842017/xrt/ssin/

Downloading 00030842017:   0%|          | 0/109 [00:00<?, ?files/s]

Making directory /tmp/APIDemo_download2/00030842015
Making directory /tmp/APIDemo_download2/00030842015/xrt/
Making directory /tmp/APIDemo_download2/00030842015/xrt/event/
Making directory /tmp/APIDemo_download2/00030842015/xrt/hk/
Making directory /tmp/APIDemo_download2/00030842015/xrt/ssin/

Downloading 00030842015:   0%|          | 0/99 [00:00<?, ?files/s]

Making directory /tmp/APIDemo_download2/00030842012
Making directory /tmp/APIDemo_download2/00030842012/xrt/
Making directory /tmp/APIDemo_download2/00030842012/xrt/event/
Making directory /tmp/APIDemo_download2/00030842012/xrt/hk/
Making directory /tmp/APIDemo_download2/00030842012/xrt/ssin/

Downloading 00030842012:   0%|          | 0/99 [00:00<?, ?files/s]

Making directory /tmp/APIDemo_download2/00030842005
Making directory /tmp/APIDemo_download2/00030842005/xrt/
Making directory /tmp/APIDemo_download2/00030842005/xrt/event/
Making directory /tmp/APIDemo_download2/00030842005/xrt/hk/
Making directory /tmp/APIDemo_download2/00030842005/xrt/ssin/

Downloading 00030842005:   0%|          | 0/114 [00:00<?, ?files/s]

If you want to check your filter expression before actually using it, you can use the .loc property of a DataFrame like this:

q.results.loc[q.results['xrt_exposure']>6000]
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
0 176.435074 GKPer 30842 52.732920 43.912266 255.973292 2007-03-06T04:39:01 2007-03-06T14:14:01 17 00030842017 7951 7846.457 51.297 7795.160 7848.622 +03h 30m 55.90s 52d43m58.512s +43d 54m 44.2s 43d54m44.1576s
1 168.121066 GKPer 30842 52.745146 43.929141 260.414911 2007-02-26T00:25:01 2007-02-26T06:08:24 15 00030842015 6488 6403.276 32.808 6370.468 6418.423 +03h 30m 58.84s 52d44m42.5256s +43d 55m 44.9s 43d55m44.9076s
2 154.223024 GKPer 30842 52.740645 43.906271 262.071536 2007-02-16T02:39:01 2007-02-16T08:20:54 12 00030842012 6303 6238.041 82.605 6155.436 6237.197 +03h 30m 57.75s 52d44m26.322s +43d 54m 22.6s 43d54m22.5756s
3 25.319939 GKPer 30842 52.793185 43.899291 280.915675 2007-01-17T03:10:01 2007-01-17T13:41:49 5 00030842005 6053 6078.284 60.784 6017.500 5862.453 +03h 31m 10.36s 52d47m35.466s +43d 53m 57.4s 43d53m57.4476s

And you can see that the DataFrame we got back contains only the rows we wanted.

I'll give a few more examples of creating subsets, and rather then fill up your /tmp area by downloading, I'll define them as variables and print them using .loc, but you can just put my examples straight into the subset argument of downloadObsData. I'll include such code but commented out, below.

First, let's show you how to apply multiple filters because this always take me 3 attempts to get right. Let's say we only want results with more than 6ks of XRT exposure, but less than 7ks.

subset = (q.results['xrt_exposure']>6000)&(q.results['xrt_exposure']<7000)
q.results.loc[subset]
# Uncomment the following if you want:

# q.downloadObsData(destDir='/tmp/APIDemo_download3',
#                   subset=(q.results['xrt_exposure']>6000)&(q.results['xrt_exposure']<7000),
#                   instruments=('XRT',),
#                   getAuxil=False)
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
1 168.121066 GKPer 30842 52.745146 43.929141 260.414911 2007-02-26T00:25:01 2007-02-26T06:08:24 15 00030842015 6488 6403.276 32.808 6370.468 6418.423 +03h 30m 58.84s 52d44m42.5256s +43d 55m 44.9s 43d55m44.9076s
2 154.223024 GKPer 30842 52.740645 43.906271 262.071536 2007-02-16T02:39:01 2007-02-16T08:20:54 12 00030842012 6303 6238.041 82.605 6155.436 6237.197 +03h 30m 57.75s 52d44m26.322s +43d 54m 22.6s 43d54m22.5756s
3 25.319939 GKPer 30842 52.793185 43.899291 280.915675 2007-01-17T03:10:01 2007-01-17T13:41:49 5 00030842005 6053 6078.284 60.784 6017.500 5862.453 +03h 31m 10.36s 52d47m35.466s +43d 53m 57.4s 43d53m57.4476s

And lastly, the isin function which is really handy as well. This lets us make a subset by giving some values we want a column to contain.So, for example, imagine I only wanted to download the data in the above for those cases with a "target_id" of 81445, 45767 or 81637. I could do that as follows:

myTargs = (81445, 45767, 81637)
subset=q.results['target_id'].isin(myTargs)
q.results.loc[q.results['target_id'].isin(myTargs)]
# q.downloadObsData(destDir='/tmp/APIDemo_download4',
#                   subset=q.results['target_id'].isin(myTargs),
#                   instruments=('XRT',),
#                   getAuxil=False)
_r name target_id ra decl roll_angle start_time stop_time obs_segment obsid bat_exposure xrt_exposure xrt_expo_wt xrt_expo_pc uvot_exposure ra_s ra_apy decl_s decl_apy
12 89.880318 GK_Per 81445 52.829137 43.890741 63.483317 2015-09-09T09:14:58 2015-09-09T18:04:10 2 00081445002 4458 4419.383 0.040 4419.343 4350.644 +03h 31m 18.99s 52d49m44.8932s +43d 53m 26.7s 43d53m26.6676s
14 150.109990 GKPer 45767 52.743700 43.913840 249.208788 2013-03-20T01:02:59 2013-03-21T01:20:22 5 00045767005 4143 4121.740 63.538 4058.202 4087.774 +03h 30m 58.49s 52d44m37.32s +43d 54m 49.8s 43d54m49.824s
23 34.471666 GK_Per 81637 52.812729 43.901448 59.655334 2015-09-08T20:24:58 2015-09-08T22:15:56 1 00081637001 2047 2336.794 0.000 2336.794 2317.685 +03h 31m 15.05s 52d48m45.8244s +43d 54m 5.2s 43d54m05.2128s
25 35.612521 GKPer 45767 52.787061 43.901074 87.297491 2012-07-07T06:29:59 2012-07-07T21:44:45 2 00045767002 0 2056.139 0.169 2055.970 2046.231 +03h 31m 8.89s 52d47m13.4196s +43d 54m 3.9s 43d54m03.8664s
34 117.912554 GK_Per 81445 52.754590 43.904928 243.739529 2015-03-31T19:32:59 2015-03-31T21:43:07 1 00081445001 1899 1894.012 1894.012 0.000 1859.323 +03h 31m 1.10s 52d45m16.524s +43d 54m 17.7s 43d54m17.7408s
88 30.286534 GKPer 45767 52.793330 43.897413 84.556936 2012-07-10T00:12:59 2012-07-10T21:55:42 4 00045767004 240 992.861 0.000 992.861 960.151 +03h 31m 10.40s 52d47m35.988s +43d 53m 50.7s 43d53m50.6868s
94 43.622898 GKPer 45767 52.802583 43.892319 87.346896 2012-07-09T01:47:59 2012-07-09T02:53:33 3 00045767003 0 977.932 0.093 977.839 966.009 +03h 31m 12.62s 52d48m09.2988s +43d 53m 32.3s 43d53m32.3484s

Astute readers will have realised that this effectively takes place of the IN operator in SQL, which is not supported by the addFilter command (largely for security reasons).