import re
import numpy as np
from dascore.core import get_coord, get_coord_manager
regular = get_coord(start=0, stop=10, step=1, units="m")
same = get_coord(data=np.arange(10), units="m")
irregular = get_coord(data=np.array([0.0, 1.0, 2.2, 4.0]))
assert regular == sameCoordinates and Coordinate Managers
This page covers advanced features. Most users should work through the Patch coordinate interface.
BaseCoord represents one coordinate. CoordManager groups coordinates and keeps their associated arrays aligned. Both return new objects rather than modifying themselves.
Coordinates
Creation
get_coord chooses the appropriate coordinate type from a range or array.
Coordinates expose values, units, data type, ordering, sampling regularity, limits, and length through their attributes and methods.
| Attribute | Meaning |
|---|---|
sorted |
Whether the coordinate reports ascending ordering |
reverse_sorted |
Whether the coordinate reports descending ordering |
evenly_sampled |
Whether one step describes every value |
dtype |
NumPy data type |
data |
Array of coordinate values |
units |
Physical units, if present |
degenerate |
Whether the coordinate is empty or zero-dimensional |
min(), max() |
Coordinate limits |
Segmented coordinates
CoordSegmented preserves monotonic blocks and their discontinuities. Use concat_coords(...) when the blocks are known:
from dascore.core.coords import concat_coords
first = get_coord(start=0.0, stop=5.0, step=1.0, units="m")
second = get_coord(start=8.0, stop=13.0, step=1.0, units="m")
segmented = concat_coords(first, second)
print(segmented.get_discontinuities("gaps")) index before after delta excess
0 5 4.0 8.0 4.0 3.0
Use CoordSegmented.from_array(...) to preserve an exact monotonic array. It returns the simplest truthful coordinate, so uniform input becomes CoordRange; segmented output has step=None. simplify(tolerance=...) may reduce segments within an error bound, while snap() replaces values with one evenly sampled range. Use patch.split_gaps() before writing segmented dimensions to formats that cannot represent gaps.
from dascore.core.coords import CoordSegmented
values = np.array([0.0, 1.0, 2.0, 7.0, 8.0, 9.0])
exact = CoordSegmented.from_array(values, tolerance=0, units="m")
assert np.array_equal(exact.values, values)Updating and units
update uses an existing coordinate as a template:
start = np.datetime64("2023-01-01")
coord = get_coord(start=start, stop=start + np.timedelta64(1, "h"), step=np.timedelta64(1, "m"))
shifted = coord.update(data=coord.values + np.timedelta64(10, "s"))
coarser = coord.update(step=np.timedelta64(30, "m"))Updating min, max, or step retains the coordinate length and derives the remaining range values. Updating data instead lets DASCore choose the simplest coordinate class that represents the new array.
set_units changes the label without changing values; convert_units converts both.
relabeled = regular.set_units("ft")
converted = regular.convert_units("ft")Sorting, snapping, and selecting
Methods that reorder or remove samples return both a coordinate and an indexer for the associated array.
unsorted = get_coord(data=np.array([3, 1, 2]))
sorted_coord, indexer = unsorted.sort()
data = np.arange(6).reshape(3, 2)
sorted_data = data[indexer, :]
selected_coord, indexer = regular.select((3, 7))
selected_data = np.arange(len(regular))[indexer]Selections accept compatible quantities and convert them before comparison:
from dascore.units import ft
selected_in_feet, _ = regular.select((3 * ft, 20 * ft))sort permutes existing values and returns the matching permutation. snap changes values, while simplify(tolerance=...) chooses a simpler representation only within the requested displacement bound. The distinction is important whenever coordinate values are measurements rather than ideal labels.
snap() returns only a coordinate because it replaces values rather than permuting them. Use CoordManager.snap or Patch.snap_coords when data must be sorted with the coordinate before snapping.
String coordinates
String coordinates support exact matches, * and ? wildcards, compiled regular expressions, boolean masks, and sorting. They do not support units, relative selection, or numeric ranges.
labels = get_coord(data=np.array(["ch_a", "ch_b", "aux_1", "aux_2"]))
exact, _ = labels.select("ch_b")
channels, _ = labels.select("ch_*")
aux, _ = labels.select(re.compile(r"^aux_\d$"))
sorted_labels, sort_indexer = labels.sort()Strings containing * or ? are treated as wildcard patterns, so avoid those characters in labels which must be matched literally. Boolean masks are the unambiguous choice for arbitrary sets.
Finding insertion positions
get_next_index returns where a value is or would be inserted in a sorted coordinate:
assert regular.get_next_index(2) == 2
assert regular.get_next_index(2.1) == 3Coordinate managers
Creation and association
get_coord_manager creates a manager from coordinates and ordered dimensions. Tuple values specify (associated_dimensions, coordinate); use None for an independent coordinate.
distance = get_coord(start=0, stop=10, step=1)
time = get_coord(start=0, stop=1, step=0.1)
latitude = get_coord(data=np.linspace(41.5, 41.6, len(distance)))
manager = get_coord_manager(
coords={
"distance": distance,
"time": time,
"latitude": ("distance", latitude),
"deployment": (None, get_coord(data=np.array(["A"]))),
},
dims=("distance", "time"),
)manager.dims stores dimension order, coord_map maps names to coordinates, and dim_map records dimensional associations.
print(manager.dims)
print(dict(manager.dim_map))('distance', 'time')
{'distance': ('distance',), 'time': ('time',), 'latitude': ('distance',), 'deployment': ()}
Updating
CoordManager.update replaces, adds, disassociates, or drops coordinates:
new_distance = distance.update(data=distance.values + 10)
updated = manager.update(distance=new_distance)
detached = updated.update(latitude=(None, latitude))
without_latitude = detached.update(latitude=None)New coordinates use the same update form:
quality = get_coord(data=np.linspace(0, 1, len(distance)))
with_quality = manager.update(quality=("distance", quality))
independent = manager.update(run=(None, get_coord(data=np.array([1]))))Setting a dimension coordinate to None drops that dimension and every coordinate depending on it. Dropping a missing non-dimensional coordinate is a no-op.
Methods with data
Operations that change array alignment accept the array and return an updated manager and array. Coordinate names are keyword arguments, as on Patch.
import dascore as dc
patch = dc.get_example_patch()
manager, data = patch.coords, patch.data
selected_manager, selected_data = manager.select(data=data, distance=(..., 100))
sorted_manager, sorted_data = manager.sort("time", "distance", array=data, reverse=True)
renamed = manager.rename_coord(time="elapsed_time")When several dimensions are sorted together, the returned data reflects each permutation in dimension order. Renaming a dimension also updates every coordinate association that refers to it.
select, order, sort, and snap accept an associated array and return (manager, array). Methods that do not change alignment, such as unit conversion, return only a manager.
Prefer the corresponding Patch methods when operating on a patch; they keep data, coordinates, metadata, and provenance together.