Creating a 🌈 from Arrays¶
You can create a Rainbow
object from arrays representing wavelength, time, flux, and any other array quantities that have the same shape as one of those first three (see Basics of 🌈 Objects). Here, we show how to construct 🌈s from arrays by creating some (very cartoonish) simulated datasets of time-series spectra.
from chromatic import Rainbow, RainbowWithModel, SimulatedRainbow, version
from chromatic import np, plt, u
version()
'0.4.12'
Shortest Example¶
This example shows, in the fewest code lines possible, how to create a 🌈 by supplying your own custom arrays. It doesn't explain things very carefully though, so please read on to the other examples for a friendlier introduction. In this example, we populate a 🌈 with both data and model, but if you don't plan to use the any model comparison features you can simply skip the model=
keyword.
N_wavelengths, N_times = 13, 17
r = RainbowWithModel(
wavelength=np.linspace(1, 2, N_wavelengths) * u.micron,
time=np.linspace(-0.1, 0.1, N_times) * u.day,
flux=np.random.normal(1, 0.01, [N_wavelengths, N_times]),
uncertainty=np.ones([N_wavelengths, N_times]) * 0.01,
ok=np.random.uniform(0, 1, [N_wavelengths, N_times]) > 0.1,
model=np.ones([N_wavelengths, N_times]),
)
That's it! We've created a new 🌈 object just by supplying a few arrays. To make sure it worked, let's make a plot.
r.plot_with_model();
Simplest Example¶
For the simplest example, we'll make a 🌈 out of wavelength
, time
, and flux
.
First, let's create an array of wavelengths. We'll use astropy units to specify that the units of wavelength are in micron. Setting the units explicitly helps save us from confusion and ruin later on!
N_wavelengths = 7
my_neat_wavelengths = np.linspace(0.5, 5, N_wavelengths) * u.micron
my_neat_wavelengths
Next, let's create some times. Again, we'll give them units of time.
N_times = 11
my_swell_times = np.linspace(-0.1, 0.1, N_times) * u.day
my_swell_times
And finally, let's make some fluxes associated with each of these wavelengths and times. In general, you'll want to assemble this array of fluxes out of a series of spectra or a group of light curves, but for this example the flux will just be totally random. The first dimension (row) of this array should correspond to wavelength, and the second (column) to time.
my_great_fluxes = np.random.normal(1, 0.01, size=(N_wavelengths, N_times))
With those arrays, we can create a 🌈 by feeding them in as keywords to Rainbow
:
r = Rainbow(wavelength=my_neat_wavelengths, time=my_swell_times, flux=my_great_fluxes)
Ta-da! Now those wavelengths, times, and fluxes have been connected into one 🌈!
r
<🌈(7w, 11t)>
r.imshow();
Slightly More Complicated Example¶
For a tiny bit more complexity, let's also add uncertainties when defining our 🌈.
We'll use the same wavelength and time grids as before, but let's define some uncertainties and fluxes together.
my_cool_uncertainties = np.ones((N_wavelengths, N_times))
my_cool_uncertainties *= np.linspace(0.01, 0.05, N_wavelengths)[:, np.newaxis]
my_cool_fluxes = np.random.normal(1, my_cool_uncertainties)
To include the uncertainty values, just add an uncertainty
keyword:
r = Rainbow(
wavelength=my_neat_wavelengths,
time=my_swell_times,
flux=my_cool_fluxes,
uncertainty=my_cool_uncertainties,
)
Huzzah! Now there's a rainbow that has an uncertainty associated with each flux. These uncertainties will be helpful if you want to compare your data to models, or if for downweighting more uncertain points when binning together in time or wavelength.
fi, ax = plt.subplots(1, 2, figsize=(10, 3))
r.imshow(quantity="uncertainty", ax=ax[0])
r.imshow(ax=ax[1]);
Most Comprehensive Example¶
For completeness, let's also add some more quantities that align with either the wavelengths, the times, or the fluxes.
Imagine you have a time series of centroid positions (one for each time), or perhaps you recorded the background flux that was subtracted during spectral extraction (one for each wavelength and time), or you have other quantities that would be useful to keep connected to your time-series spectra. Let's make some of these, as examples:
my_wobbly_centroids = np.random.normal(5, 0.02, N_times) * u.pixel
my_messy_backgrounds = (
np.random.normal(10, 0.1, (N_wavelengths, N_times)) * u.photon / u.s
)
my_stellar_spectrum = np.random.uniform(2, 3, N_wavelengths) * u.W / u.m**2
You can populate additional arrays inside a rainbow by providing them as additional keyword arguments. Any names are allowed, except for a few protected keywords (filepath
, format
, wavelike
, timelike
, fluxlike
, metadata
).
r = Rainbow(
wavelength=my_neat_wavelengths,
time=my_swell_times,
flux=my_cool_fluxes,
uncertainty=my_cool_uncertainties,
centroid=my_wobbly_centroids,
background=my_messy_backgrounds,
stellar_spectrum=my_stellar_spectrum,
)
Arrays will be sorted into wavelike
, timelike
, and fluxlike
dictionaries based on their shape.
r.wavelike
{'wavelength': <Quantity [0.5 , 1.25, 2. , 2.75, 3.5 , 4.25, 5. ] micron>, 'stellar_spectrum': <Quantity [2.37320554, 2.21146813, 2.77951229, 2.35138747, 2.46937388, 2.17141199, 2.95852714] W / m2>, 'original_wave_index': array([0, 1, 2, 3, 4, 5, 6])}
r.timelike
{'time': <Quantity [-0.1 , -0.08, -0.06, -0.04, -0.02, 0. , 0.02, 0.04, 0.06, 0.08, 0.1 ] d>, 'centroid': <Quantity [4.99598048, 5.01016956, 4.99385771, 4.98842844, 5.0077778 , 4.99376379, 4.99125139, 4.98600703, 4.98646825, 5.00001029, 4.97967375] pix>, 'original_time_index': array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])}
r.fluxlike
{'flux': array([[0.98483517, 1.00341052, 1.00453097, 1.00630223, 1.00668405, 0.99904088, 1.00486895, 0.98071348, 0.99085236, 0.98832239, 0.99089689], [0.99569813, 1.00435474, 1.01070497, 1.01272242, 1.00374394, 1.0044368 , 0.99091978, 1.01450545, 0.98548995, 0.99215262, 1.00842704], [1.0041301 , 1.00394588, 0.97902838, 1.00028776, 1.01255133, 0.9597281 , 1.03159081, 1.01810881, 0.97673858, 1.00708515, 0.99397732], [0.96387381, 0.94252017, 0.98418521, 0.96288307, 0.98188025, 1.04629649, 1.02008364, 1.0160117 , 0.96321747, 1.0061836 , 0.98217088], [0.97883921, 1.05895637, 0.96196109, 0.99647146, 1.00799308, 1.08978104, 0.98901566, 1.0197976 , 1.02549395, 1.01918926, 1.05067579], [0.94707165, 0.91724097, 0.9884112 , 0.96033325, 0.99150391, 1.03128104, 1.01444065, 1.08068645, 0.98780804, 1.0888794 , 0.95588974], [0.97627025, 1.030089 , 1.00374527, 1.0308361 , 0.99232412, 1.06000939, 1.04423565, 0.89427502, 0.97332907, 1.07496436, 0.97046394]]), 'uncertainty': array([[0.01 , 0.01 , 0.01 , 0.01 , 0.01 , 0.01 , 0.01 , 0.01 , 0.01 , 0.01 , 0.01 ], [0.01666667, 0.01666667, 0.01666667, 0.01666667, 0.01666667, 0.01666667, 0.01666667, 0.01666667, 0.01666667, 0.01666667, 0.01666667], [0.02333333, 0.02333333, 0.02333333, 0.02333333, 0.02333333, 0.02333333, 0.02333333, 0.02333333, 0.02333333, 0.02333333, 0.02333333], [0.03 , 0.03 , 0.03 , 0.03 , 0.03 , 0.03 , 0.03 , 0.03 , 0.03 , 0.03 , 0.03 ], [0.03666667, 0.03666667, 0.03666667, 0.03666667, 0.03666667, 0.03666667, 0.03666667, 0.03666667, 0.03666667, 0.03666667, 0.03666667], [0.04333333, 0.04333333, 0.04333333, 0.04333333, 0.04333333, 0.04333333, 0.04333333, 0.04333333, 0.04333333, 0.04333333, 0.04333333], [0.05 , 0.05 , 0.05 , 0.05 , 0.05 , 0.05 , 0.05 , 0.05 , 0.05 , 0.05 , 0.05 ]]), 'background': <Quantity [[ 9.91171728, 10.00603295, 10.03349739, 9.95347253, 10.20308178, 9.89790344, 10.03639581, 10.00153481, 9.90779213, 9.9720355 , 10.0641662 ], [10.04549874, 10.09830611, 9.90240632, 9.96273173, 10.1495607 , 10.04131678, 9.73645074, 9.96308649, 9.83163997, 10.11588529, 10.01765208], [ 9.98179654, 9.96306812, 10.09197344, 10.10448501, 9.83164478, 10.05009155, 9.83953823, 9.910974 , 9.93656662, 10.07896801, 9.94151286], [10.10281939, 9.89195383, 9.86102175, 9.9510688 , 9.97529703, 9.85651107, 9.90999804, 9.91638155, 10.08943757, 10.07864749, 10.16447281], [ 9.91389588, 9.94873291, 9.96722832, 10.04532251, 9.91263763, 10.21670902, 10.08306905, 9.90651551, 9.99991681, 9.95257595, 9.99529273], [10.18795863, 9.94270903, 9.96945337, 9.91892956, 9.9845038 , 9.99300229, 9.80840165, 9.97475741, 10.0261776 , 10.08150022, 9.94117976], [ 9.90325879, 10.03754554, 10.01789512, 9.97723099, 9.89591369, 9.90151157, 10.04588605, 10.03796851, 9.94808227, 9.99024962, 10.13732861]] ph / s>}
Let's also attach a model to our Rainbow
to unlike some snazzy model plotting features. The .attach_model()
action can be used to do this.
m = r.attach_model(model=np.ones_like(my_cool_fluxes))
type(r)
chromatic.rainbows.rainbow.Rainbow
type(m)
chromatic.rainbows.withmodel.RainbowWithModel
The .attach_model()
action generates a new type of object, the RainbowWithModel
. These objects have more abilities that require comparing data to a model. Below, we can call m.plot_with_model()
but we wouldn't be able to call r.plot_with_model()
because r
doesn't have any model associated with it.
m.plot_with_model();
You can also add arrays directly to a core dictionary by (a) providing a key and an array with the right shape or (b) setting an attribute with an array that would fit in one of the timelike
, wavelike
, or fluxlike
dictionaries. The latter option will try to guess where an array belongs based on its shape, which should mostly work. The following two methods should be identical:
my_imaginary_temperatures = np.random.normal(77, 0.3, N_times)
r.timelike["detector_temperature"] = my_imaginary_temperatures
r.detector_temperature = my_imaginary_temperatures
Metadata Example¶
Except for some protected words (wavelength
, time
, flux
, uncertainty
, ok
, model
, other class method names, and a few others), any other attributes you set for a rainbow object will be stored in the .metadata
core dictionary, so they can be saved and shared. This can be a nice way to document important human-readable information that's useful for interpretting the data.
s = SimulatedRainbow()
s.author = "Zach Berta-Thompson"
s.warning = "Watch out! These data are entirely imaginary!"
You can also edit the .metadata
dictionary directly.
s.metadata["and another thing"] = "Be kind!"
When we look at the metadata, you'll notice there are a already few other entries that have been automatically populated. If at all possible, it's probably best to try to avoid overwriting those.
s.metadata
{'name': None, 'history': ["SimulatedRainbow(\n tlim=u.Quantity(np.array([-2.5, 2.5]))*u.Unit('h'),\n dt=u.Quantity(2.0)*u.Unit('min'),\n wlim=u.Quantity(np.array([0.5, 5. ]))*u.Unit('micron'),\n R=100)"], 'R': 100, 'wscale': 'log', 'tscale': 'linear', 'author': 'Zach Berta-Thompson', 'warning': 'Watch out! These data are entirely imaginary!', 'and another thing': 'Be kind!'}
Wahoo! You've done it! Now you can create a 🌈 from whatever arrays and/or data you have available!