Skip to content

Commit

Permalink
Merge pull request #76 from mommermi/phys
Browse files Browse the repository at this point in the history
data.Phys initial implementation
  • Loading branch information
mommermi committed Oct 16, 2018
2 parents 3d97ae9 + bae77e6 commit c9000b7
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 43 deletions.
27 changes: 26 additions & 1 deletion docs/sbpy/data.rst
Expand Up @@ -520,7 +520,32 @@ Note that both functions require pyoorb to be installed, which is not a requirem

How to use Phys
---------------
tbd

`~sbpy.data.Phys` is designed to contain query physical properties for
small bodies; functions to query these properties are
available. `~sbpy.data.Phys.from_sbdb` queries the `JPL Small-body
Database Browser (SBDB)<https://ssd.jpl.nasa.gov/sbdb.cgi>`_ for physical
properties and stores the data in a `~sbpy.data.Phys` object, offering
the same functionality as all the other `~sbpy.data` functions,
including the use of `~astropy.units`.

As an example, the following code will query the properties for a
small number of asteroids:

>>> from sbpy.data import Phys
>>> phys = Phys.from_sbdb(['Ceres', '12893', '3552'])
>>> print(phys['targetname', 'H', 'diameter']) # doctest: +SKIP
targetname H diameter
mag km
-------------------------- ------------------ --------
1 Ceres 3.34 939.4
12893 Mommert (1998 QS55) 13.9 5.214
3552 Don Quixote (1983 SA) 12.800000000000002 19.0

Please note that the SBDB database is not complete with respect to
physical properties and should be considered as a sparse dataset.




How to use Names
Expand Down
40 changes: 26 additions & 14 deletions sbpy/data/core.py
Expand Up @@ -326,26 +326,33 @@ def column_names(self):
"""Return a list of all column names in the data table."""
return self._table.columns

def add_rows(self, rows):
def add_rows(self, rows, join_type='inner'):
"""Append additional rows to the existing data table. An individual
row can be provided in list, tuple, `~numpy.ndarray`, or
dictionary form. Multiple rows can be provided in the form of
a list, tuple, or `~numpy.ndarray` of individual
rows. Multiple rows can also be provided in the form of a
`~astropy.table.QTable` or another `~sbpy.data.DataClass`
object. In case of a dictionary, `~astropy.table.QTable`, or
`~sbpy.data.DataClass`, all table column names must be
provided in ``row``; additional keys that are not yet column
names in the table will be discarded. In case of a list, the
list elements must be in the same order as the table
columns. In either case, matching `~astropy.units` must be
provided in ``rows`` if used in the data table.
object. Parameter ``join_type`` defines which columns appear
in the final output table: ``inner`` only keeps those columns
that appear in both the original table and the rows to be
added; ``outer`` will keep all columns and populate some with
placeholders, if necessary. In case of a list, the list
elements must be in the same order as the table columns. In
either case, matching `~astropy.units` must be provided in
``rows`` if used in the data table.
Parameters
----------
rows : list, tuple, `~numpy.ndarray`, dict, or `~collections.OrderedDict`
data to be appended to the table; required to have the same
length as the existing table, as well as the same units
Data to be appended to the table; required to have the same
length as the existing table, as well as the same units.
join_type : str, optional
Defines which columns are kept in the output table: ``inner``
only keeps those columns that appear in both the original
table and the rows to be added; ``outer`` will keep all
columns and populate them with placeholders, if necessary.
Default: ``inner``
Returns
-------
Expand Down Expand Up @@ -374,11 +381,13 @@ def add_rows(self, rows):
6
>>> dat.add_rows(dat)
12
"""
if isinstance(rows, QTable):
self._table = vstack([self._table, rows])
self._table = vstack([self._table, rows], join_type=join_type)
if isinstance(rows, DataClass):
self._table = vstack([self._table, rows.table])
self._table = vstack([self._table, rows.table],
join_type=join_type)
if isinstance(rows, (dict, OrderedDict)):
try:
newrow = [rows[colname] for colname in self._table.columns]
Expand All @@ -396,7 +405,7 @@ def add_rows(self, rows):
self._table.add_row(rows)
return len(self._table)

def add_column(self, data, name):
def add_column(self, data, name, **kwargs):
"""Append a single column to the current data table. The lenght of
the input list, `~numpy.ndarray`, or tuple must match the current
number of rows in the data table.
Expand All @@ -409,6 +418,9 @@ def add_column(self, data, name):
name : str
Name of the new column; must be different from already existing
column names.
**kwargs : additional parameters
Additional optional parameters will be passed on to
`~astropy.table.Table.add_column`.
Returns
-------
Expand All @@ -433,7 +445,7 @@ def add_column(self, data, name):
3.0 6.0 c 30.0
"""

self._table.add_column(Column(data, name=name))
self._table.add_column(Column(data, name=name), **kwargs)
return len(self.column_names)

def _translate_columns(self, target_colnames):
Expand Down
152 changes: 124 additions & 28 deletions sbpy/data/phys.py
@@ -1,16 +1,22 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
======================
SBPy data.Phys Module
sbpy data.Phys Module
=====================
Class for storing and querying physical properties
created on June 04, 2017
"""

from collections import OrderedDict

from numpy import ndarray, array, isnan, nan
import astropy.units as u
from astroquery.jplsbdb import SBDB

from .core import DataClass
from .. import bib

__all__ = ['Phys']

Expand All @@ -19,32 +25,136 @@ class Phys(DataClass):
"""Class for storing and querying physical properties"""

@classmethod
def from_horizons(cls, targetid, bib=None):
"""Load physical properties from JPL Horizons
(https://ssd.jpl.nasa.gov/horizons.cgi)
def from_sbdb(cls, targetids, references=False, notes=False):
"""Load physical properties from `JPL Small-Body Database (SBDB)
<https://ssd.jpl.nasa.gov/sbdb.cgi>`_ using
`~astroquery.jplsbdb` for one or more targets. Builds a
`~Phys` object from the output of `'phys_par'` from
SBDB. Units are applied, where available. Missing data are
filled up as nan values. Note that SBDB only serves physical
properties data for a limited number of objects.
Parameters
----------
targetid : str, mandatory
target identifier
bib : SBPy Bibliography instance, optional, default None
Bibliography instance that will be populated
targetids : str, int or iterable thereof
Target identifier(s) to be queried; use object numbers, names,
or designations as unambiguous as possible.
Returns
-------
Astropy Table
`~Phys` object
Examples
--------
>>> from sbpy.data import Phys # doctest: +SKIP
>>> phys = Phys.from_horizons('Ceres') # doctest: +SKIP
not yet implemented
>>> from sbpy.data import Phys
>>> phys = Phys.from_sbdb(['Ceres', '12893', '3552'])
>>> print(phys['targetname', 'H', 'diameter']) # doctest: +SKIP
targetname H diameter
mag km
-------------------------- ------------------ --------
1 Ceres 3.34 939.4
12893 Mommert (1998 QS55) 13.9 5.214
3552 Don Quixote (1983 SA) 12.800000000000002 19.0
"""

if not isinstance(targetids, (list, ndarray, tuple)):
targetids = [targetids]

alldata = []
columnnames = ['targetname']
columnunits = OrderedDict([('targetname', set())])
for targetid in targetids:

sbdb = SBDB.query(str(targetid), phys=True)

# assemble data from sbdb output
data = OrderedDict([('targetname', sbdb['object']['fullname'])])
for key, val in sbdb['phys_par'].items():
if val is None or val == 'None':
val = nan
if '_note' in key:
if notes:
data[key] = val
elif '_ref' in key:
if references:
data[key] = val
else:
try:
if isnan(val):
val = nan
except TypeError:
pass
data[key] = val

# add to columnnames if not yet there
if key not in columnnames:
columnnames.append(key)
columnunits[key] = set()

# identify units
if isinstance(val, u.Quantity):
columnunits[key].add(val.unit)
elif isinstance(val, u.CompositeUnit):
for unit in val.bases:
columnunits[key].add(unit)

alldata.append(data)

# re-assemble data on a per-column basis
coldata = []
for col in columnnames:
data = []

for obj in alldata:
try:
data.append(obj[col])
except KeyError:
data.append(nan)

# identify common unit (or at least any unit)
try:
unit = list(columnunits[col])[0]
# transform data to this unit
newdata = []
for dat in data:
if isinstance(dat, (u.Quantity, u.CompositeUnit)):
try:
newdata.append(dat.to(unit))
except u.UnitConversionError:
# keep data untouched if conversion fails
unit = 1
newdata = data
break
else:
newdata.append(dat)
except IndexError:
# data has no unit assigned
unit = 1
newdata = data

# convert lists of strings to floats, where possible
try:
data = array(newdata).astype(float)
except (ValueError, TypeError):
data = newdata

# apply unit, if available
if unit != 1:
coldata.append(data*unit)
else:
coldata.append(data)

if bib.status() is None or bib.status():
bib.register('sbpy.data.Phys.from_sbdb',
{'data service url':
'https://ssd.jpl.nasa.gov/sbdb.cgi'})

# assemble data as Phys object
return cls.from_array(coldata, names=columnnames)

@classmethod
def from_lowell(cls, targetid, bib=None):
def from_lowell(cls, targetid):
"""Load physical properties from Lowell Observatory
(http://asteroid.lowell.edu/).
Expand All @@ -55,8 +165,6 @@ def from_lowell(cls, targetid, bib=None):
----------
targetid : str, mandatory
target identifier
bib : SBPy Bibliography instance, optional, default None
Bibliography instance that will be populated
Returns
-------
Expand All @@ -70,15 +178,3 @@ def from_lowell(cls, targetid, bib=None):
not yet implemented
"""

def derive_absmag(self):
"""Derive absolute magnitude from diameter and geometric albedo"""

def derive_diam(self):
"""Derive diameter from absolute magnitude and geometric albedo"""

def derive_pv(self):
"""Derive geometric albedo from diameter and absolute magnitude"""

def derive_bondalbedo(self):
"""Derive Bond albedo from geometric albedo and photometric phase slope"""
25 changes: 25 additions & 0 deletions sbpy/data/tests/test_phys_remote.py
@@ -0,0 +1,25 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst

import pytest

from sbpy.data import Phys
from sbpy import bib


@pytest.mark.remote_data
def test_from_sbdb():
""" test from_horizons method"""

# query one object
data = Phys.from_sbdb('Ceres')
assert len(data.table) == 1

# query several objects
with bib.Tracking():
data = Phys.from_sbdb([n+1 for n in range(5)])
assert len(data.table) == 5

assert data['H'].unit == 'mag'
assert data['G'].unit is None

#assert 'sbpy.data.Phys.from_sbdb' in bib.to_text()

0 comments on commit c9000b7

Please sign in to comment.