Miscellaneous Utilities

The follow demonstrates some miscellaneous utilities included in ObsPlus.

Geodetics

It is often necessary to calculate geometric parameters (distance, azimuth, etc.) for pairs of entities in two different groups. For example, distance from each event in a catalog to each receiver in an inventory. ObsPlus provides a simple class for completing this task.

[1]:
# Load the catalog and inventory from the crandall dataset
import obsplus
from obsplus.utils.geodetics import SpatialCalculator

crandall = obsplus.load_dataset("crandall_test")
cat = crandall.event_client.get_events()
inv = crandall.station_client.get_stations()
[2]:
# init a SpatialCalculator instance (defaults to Earth's params)
spatial_calc = SpatialCalculator()
[3]:
# create distance dataframe
df = spatial_calc(entity_1=cat, entity_2=inv)
df.head()
/home/runner/micromamba/envs/test/lib/python3.10/site-packages/obspy/taup/taup_geo.py:105: UserWarning: Assuming spherical planet when calculating epicentral distance. Install the Python module 'geographiclib' to solve this.
  warnings.warn(msg)
[3]:
distance_m azimuth back_azimuth distance_degrees vertical_distance_m
id1 id2
smi:local/248828 UU.SRU..HHE 72882.074942 302.534256 122.082166 0.654711 8374.0
smi:local/248843 UU.SRU..HHE 72425.955953 302.739971 122.291744 0.650613 3854.0
smi:local/248839 UU.SRU..HHE 72254.546800 303.034657 122.588954 0.649074 2214.0
smi:local/248925 UU.SRU..HHE 71648.952510 303.260251 122.819428 0.643633 6424.0
smi:local/248891 UU.SRU..HHE 71821.459138 302.940209 122.496737 0.645183 5044.0

Since a multi-index is used it provides a fairly intuitive way to look up particular event-channel pairs using a tuple of (event_id, seed_id) in conjunction with the .loc DataFrame attribute like so:

[4]:
event_id = str(cat[0].resource_id)
seed_id = "UU.MPU..HHZ"

[5]:
# or just get a particular parameter

The distances can be converted to km and the distrtibutions can be described.

[6]:
# Convert add km columns and delete m columns
m_columns = [x for x in df.columns if x.endswith("_m")]
km_columns = [x.replace("_m", "_km") for x in m_columns]

df_km = df.assign(**{x: df[y] / 1000.0 for x, y in zip(km_columns, m_columns)}).drop(
    columns=m_columns
)

# Calculate stats for source reseiver distances
df_km.describe().round(decimals=2)
[6]:
azimuth back_azimuth distance_degrees distance_km vertical_distance_km
count 408.00 408.00 408.00 408.00 408.00
mean 201.06 211.69 0.82 91.24 5.22
std 104.37 98.06 0.33 36.55 1.85
min 9.16 48.81 0.16 18.26 1.70
25% 126.47 122.45 0.63 70.46 3.69
50% 163.87 189.79 0.82 90.85 5.50
75% 293.44 307.27 1.12 124.36 6.25
max 358.12 344.77 1.29 144.00 9.31

Time

Working with dates/times can be frustrating, especially since (as of 2020) ObsPy, numpy, and pandas all use slightly different methods for working with time. ObsPlus provides some utilities to make things a little easier.

[7]:

import numpy as np import obspy import obsplus from obsplus.utils import to_datetime64, to_timedelta64, to_utc

Note: All ObsPlus datafames use numpy/pandas datatypes.

[8]:
df = obsplus.events_to_df(obspy.read_events())
[9]:
df["time"]
[9]:
0   2012-04-04 14:21:42.300
1   2012-04-04 14:18:37.000
2   2012-04-04 14:08:46.000
Name: time, dtype: datetime64[ns]

Numpy and ObsPy time differences

One difference between numpy’s datatime64 and ObsPy’s UTCDateTime is how offsets are applied. For ObsPy, numbers are simply taken as seconds, but numpy requires explicitly using timedeta64 instances.

[10]:
time_str = "2020-01-03T11:00:00"
utc = obspy.UTCDateTime(time_str)
dt64 = np.datetime64(time_str, "ns")
[11]:
# add 1/2 second to UTCDateTime
utc2 = utc + 0.5
[12]:
# however doing the same thing with datetime64 raises a TypeError
try:
    dt64 + 0.5
except TypeError:
    pass
[13]:
# so you need to use a timedelta64
dt64_2 = dt64 + np.timedelta64(500, "ms")
[14]:
# This is, of course, also the case with datetime64 columns
df["time"] + np.timedelta64(500, "ms")
[14]:
0   2012-04-04 14:21:42.800
1   2012-04-04 14:18:37.500
2   2012-04-04 14:08:46.500
Name: time, dtype: datetime64[ns]

ObsPlus provides some common converters between ObsPy and Pandas/Numpy time objects.

Converting between ObsPy and Numpy time datatypes

Conversion between ObsPy UTCDateTime and numpy datetime64 objects when needed.

[15]:
# Convert a time column to an array of ObsPy UTCDateTime objects
utc_array = to_utc(df["time"])
[16]:
# Convert back to datetime64
dt64_array = to_datetime64(utc_array)

Note: datetime64 arrays are much more efficient in terms of memory usage and computational efficiency than arrays of UTCDateTime objects.

If you prefer not to manually define timedelta64 to perform offsets, to_timedelta64 simply converts a real number to an offset in seconds.

[17]:
to_timedelta64(3.255)
[17]:
numpy.timedelta64(3255000000,'ns')