Skip to content

Commit 42f6e59

Browse files
Add files via upload
1 parent 9b0b0f3 commit 42f6e59

13 files changed

Lines changed: 842 additions & 2 deletions

BD_asteroid_characteristics.xlsx

67.6 KB
Binary file not shown.

Bus-DeMeo-results

97.9 KB
Binary file not shown.

Bus-DeMeo-results_vfinal.xlsx

126 KB
Binary file not shown.

BusDeMeoReanalysis.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
"""
2+
Working Python file to import Bus-DeMeo spectral data, Bus-DeMeo asteroid
3+
sekected orbital and physical properties, import balanced training dataset,
4+
and classify the Bus-DeMeo asteroids using the methodology described in the paper.
5+
6+
7+
"""
8+
import os
9+
import pathlib
10+
import pandas as pd
11+
import numpy as np
12+
from scipy import interpolate
13+
from sklearn import preprocessing
14+
from matplotlib import pyplot as plt
15+
from pySankey.sankey import sankey
16+
from specmap import *
17+
18+
19+
#%% load balanced training dataset
20+
training = pd.read_pickle('balanced_training_dataset2026_01_20_1620')
21+
C = 64
22+
23+
#%% Load asteroids data
24+
ast_loc = 'DeMeo_csv_files' #provide the location of local folder where the asteroid data is saved.
25+
#%% Load Bus-DeMeo asteroid spectra into a dataframe, remove NANs
26+
infolder = list(pathlib.Path(ast_loc).glob('*.csv'))
27+
count = len(infolder)
28+
wave_ = training.columns.tolist()#bal.columns.tolist()#
29+
nir_start = np.where(np.array(wave_[2:]) >= 0.44)
30+
wave =wave_[nir_start[0][0]+2:]
31+
BDdata = np.empty([len(wave),count])
32+
BDdata[:] = np.nan
33+
BDdata_sm = pd.DataFrame()
34+
BDdata_err = np.empty([len(wave),count])
35+
ast_desig = []
36+
count = 0
37+
wave_col = 1
38+
spec_col = 2
39+
err_col = 3
40+
41+
for i in range(0,len(infolder)):
42+
path,file_name = os.path.split(infolder[i])
43+
temp = pd.read_csv(infolder[i],skiprows = 60,header=None)
44+
ast_desig.append(file_name[0:-9])
45+
if temp.iloc[0,wave_col] > wave[0] and temp.iloc[-1,wave_col] <= wave[-1]:
46+
start_wave = np.where(wave >= temp.iloc[0,wave_col])
47+
end_wave = np.where(wave >= temp.iloc[-1,wave_col])
48+
49+
sp_interp = interpolate.interp1d(temp.iloc[:,wave_col], temp.iloc[:,spec_col])
50+
err_interp = interpolate.interp1d(temp.iloc[:,0], temp.iloc[:,err_col])
51+
wave_range = wave[start_wave[0][0]:end_wave[0][0]]
52+
b_ = sp_interp(wave_range)
53+
c_ = err_interp(wave_range)
54+
BDdata[start_wave[0][0]:end_wave[0][0],count] = b_
55+
BDdata_err[start_wave[0][0]:end_wave[0][0],count] = c_
56+
57+
if temp.iloc[0,wave_col] <= wave[0] and temp.iloc[-1,wave_col] <= wave[-1]:
58+
start_idx = np.where(temp.iloc[:,wave_col].round(2) >= wave[0])
59+
end_wave = np.where(wave >= temp.iloc[-1,wave_col])
60+
if start_idx[0][0] == 0:
61+
sp_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]:,wave_col], temp.iloc[start_idx[0][0]:,spec_col])
62+
err_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]:,wave_col], temp.iloc[start_idx[0][0]:,err_col])
63+
else:
64+
sp_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]-1:,wave_col], temp.iloc[start_idx[0][0]-1:,spec_col])
65+
err_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]-1:,wave_col], temp.iloc[start_idx[0][0]-1:,err_col])
66+
67+
wave_range = wave[0:end_wave[0][0]-1]
68+
b_ = sp_interp(wave_range)
69+
c_ = err_interp(wave_range)
70+
BDdata[0:end_wave[0][0]-1,count] = b_
71+
BDdata_err[0:end_wave[0][0]-1,count] = c_
72+
73+
if temp.iloc[0,wave_col] <= wave[0] and temp.iloc[-1,wave_col] >= wave[-1]:
74+
start_idx = np.where(temp.iloc[:,wave_col].round(2) == wave[0])
75+
end_idx = np.where(temp.iloc[:,wave_col] >= wave[-1])
76+
if temp.iloc[start_idx[0][0],0] > wave[0]:
77+
sp_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]-1:end_idx[0][0]+1,wave_col], temp.iloc[start_idx[0][0]-1:end_idx[0][0]+1,spec_col])
78+
err_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]-1:end_idx[0][0]+1,wave_col], temp.iloc[start_idx[0][0]-1:end_idx[0][0]+1,err_col])
79+
else:
80+
sp_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]:end_idx[0][0]+1,wave_col], temp.iloc[start_idx[0][0]:end_idx[0][0]+1,spec_col])
81+
err_interp = interpolate.interp1d(temp.iloc[start_idx[0][0]:end_idx[0][0]+1,wave_col], temp.iloc[start_idx[0][0]:end_idx[0][0]+1,err_col])
82+
83+
b_ = sp_interp(wave)
84+
c_ = err_interp(wave)
85+
BDdata[:,count] = b_
86+
BDdata_err[:,count] = c_
87+
print(f'for {count}, condition 3')
88+
count += 1
89+
print(f'count = {count}')
90+
91+
BDdata = pd.DataFrame(BDdata)
92+
BDdata.columns = ast_desig
93+
94+
BDdata.bfill(inplace = True)
95+
BDdata.ffill(inplace = True)
96+
#%% Classify Bus-DeMeo asteroids using the balanced training dataset and a logistic regression classifier.
97+
98+
wave_ = training.columns.tolist()
99+
nir_start = np.where(np.array(wave_[2:]) >= 0.44)
100+
wave =wave_[nir_start[0][0]+2:]
101+
102+
train_spec_start_idx = 2
103+
desig = ast_desig
104+
test_wave = wave
105+
test_data = BDdata
106+
norm_wave = 1
107+
108+
BDpredictions = classify(training, train_spec_start_idx, desig, test_wave, test_data, norm_wave, C)
109+
#%% Import selected characteristics of asteroids and save the classification results
110+
bd_char_file = "C:\\Users\\maggi\\Dropbox\\Work\\Research\\specmap\\BD_asteroid_characteristics.xlsx"
111+
bd_char = pd.read_excel(bd_char_file)
112+
bd__ = pd.concat([bd_char,BDpredictions],axis = 1)
113+
114+
#%% Defining confidence of predictions
115+
# this piece of code takes the pred_proba values which are generated in the classifier above
116+
# and puts them into a new list; these values are the logistic regression's predicted probability
117+
# that the spectrum being classified is in each group. To define a confidence of the prediction
118+
# we can assess how much each prediction differes from the others. If it's a strong
119+
# prediction, the standard deviation will be high: one very large value and 9 values close to zero
120+
# If the prediction is 'weak' or less confidence, the probabilities of the groups will be similar
121+
# and the standard deviation will be small.
122+
bd_std = []
123+
bd_pred_val = []
124+
for i in range(0,bd__.shape[0]):
125+
bd_pred_val.append(np.max(bd__.iloc[i,19:]))
126+
bd_std.append(np.std(bd__.iloc[i,19:]))
127+
128+
bd_std = pd.DataFrame(bd_std)
129+
bd_pred_val = pd.DataFrame(bd_pred_val)
130+
bd_ = pd.concat([bd__,bd_pred_val],axis=1)
131+
bd_ = pd.concat([bd_,bd_std],axis = 1)
132+
133+
label_encoder = preprocessing.LabelEncoder()
134+
135+
bd = bd_.sort_values(by = 'BD-complex',ascending = False)
136+
137+
#%% Rename groups from numerical identifiers into words and sort dataframe by spectral complex
138+
139+
140+
141+
label_encoder = preprocessing.LabelEncoder()
142+
143+
bd = bd_.sort_values(by = 'BD-complex',ascending = False)
144+
145+
bd.loc[bd['pred'] == 1,'pred'] = 'Hydrated CCs'
146+
bd.loc[bd['pred'] == 2,'pred'] = 'CO/CV'
147+
bd.loc[bd['pred'] == 3,'pred'] = 'CK/R/Brach'
148+
bd.loc[bd['pred'] == 4,'pred'] = 'H/L/LL/Ure'
149+
bd.loc[bd['pred'] == 5,'pred'] = 'EH/EL/Aub'
150+
bd.loc[bd['pred'] == 6,'pred'] = 'ACA/LOD'
151+
bd.loc[bd['pred'] == 7,'pred'] = 'Irons'
152+
bd.loc[bd['pred'] == 8,'pred'] = 'HEDs'
153+
bd.loc[bd['pred'] == 9,'pred'] ='CY'
154+
bd.loc[bd['pred'] == 10,'pred'] = 'Primitive CCs'
155+
156+
#%%Create a Sankey diagram for all the endmember spectral groups:
157+
others = ['T', 'R', 'Q', 'L', 'K', 'D', 'A']
158+
bd_t = bd[~bd['BD-complex'].isin(['C'])]
159+
bd_te = bd_t[~bd_t['BD-complex'].isin(['S'])]
160+
bd_tes = bd_te[~bd_te['BD-complex'].isin(['X'])]
161+
bd_test = bd_tes[~bd_tes['BD-complex'].isin(['V'])]
162+
163+
bd_2 = pd.concat([bd_test['spec_B'],bd_test['pred']],axis = 1)
164+
165+
sankey(bd_2['spec_B'],bd_2['pred'],fontsize = 7)
166+
plt.show()
167+
168+
#%% Create a Sankey diagram for the C-complex asteroids
169+
170+
bd_test = bd[bd['BD-complex'].isin(['C'])]
171+
172+
sankey(bd_2['spec_B'],bd_2['pred'],fontsize = 7)
173+
plt.show()

Dyar_etal(2023)_dataset.xlsx

2.31 MB
Binary file not shown.

README.md

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,56 @@
1-
# SpectralMeteoriteMappingTool
2-
The Spectral Meteorite Mapping Tool is a machine learning algorithm designed to analyze asteroid mineralogy using archived near-infrared meteorite spectroscopy.
1+
# Overview:
2+
3+
This repository contains the functions, data and scripts that produced the results in two publications:
4+
5+
McAdam, M. M., Dotson, J., Dyar, M. D., Hidden in plain sight: a new Main Belt population of aqueously altered and thermally metamorphosed asteroids. _Submitted to Earth and Space Sciences Journal May 2026_
6+
7+
McAdam, M. M., Dotson, J., Dyar, M. D., Spectral Meteorite Mapping Tool for Asteroid Composition Identification: uses in Planetary Defense and General Asteroid Science. (in preparation)
8+
9+
Please contact Dr. McAdam with any questions regarding the code and data: margaret.m.mcadam at nasa dot gov
10+
11+
### Dependencies:
12+
The code and scripts were created using Python version 3.11.11.
13+
I used conda: spyder-env integrated development environment to write, test and run the code and scripts presented here.
14+
15+
To run the functions and scripts, you'll need the following packages:
16+
- Numpy
17+
- Pandas
18+
- SciPy
19+
- SciKitLearn
20+
- MatplotLib
21+
- PySankey
22+
23+
24+
### Functions and scripts
25+
26+
**specmap.py** - The Spectral Meteorite Mapping Tool are several functions that create balanced training datasets and use a supervised machine learning approach (logistic regression) to classify data. The logistic regression is trained using the provided balanced dataset and indicated hyperparameter. These functions are used to classify asteroid or meteorite spectra into one of 10 groups based on their spectral similarity.
27+
28+
_classify_ function is the function that classifies near-infrared spectroscopy into one of 10 groups of meteorites using a logistic regression machine learning algorithm trained on near-infrared archival meteorite spectra. This function can be used to classify any near-infrared dataset that has the same sampling and wavelength range of the Bus-DeMeo asteroid spectra saved in the _DeMeo_csv_files_ folder.
29+
30+
_balanced_generator_ function. The training data has uneven classes and so we elected to balance our training dataset, over- and under-sampling the meteorite classification groups to create a balanced training dataset. We generated multiple balanced training datasets and selected the one that performed best for the iron and hydrated carbonaceous chondrite classification groups. The function that generates the balanced training dataset is called 'balanced_generator' in the specmap.py code. The balanced_generator function uses the data saved in trainTestSplitTraining.xlsx to created balanced training datasets.
31+
32+
There are two other functions in this file, _normalize_ and _renormalize_, which are called in the function 'classify'. Data normalization is a rescaling of data. In the spectroscopy context, we divide the whole spectrum by one particular datapoint in the spectrum; that point becomes unity and the rest of the data have the same attributes (absorption bands, slope etc.) as the raw data but are now scaled. Spectral normalization allows us to directly compare spectral features of different spectra. The _renormalize_ function takes any data, including previously normalized data, and renormalizes it to the user-defined normalization point. The _normalize_ function takes unnormalized data and normalizes it at the user-defined wavelength.
33+
34+
**BusDeMeoReanalysis.py** - This file is the script used to ingest, classify and generate the results presented in the publications listed above. We have not included all the plots for the figures in the papers but two of the Sankey diagrams are demonstrated.
35+
36+
### Pickle files:
37+
38+
**balanced_training_dataset2026_01_20_1620** - This file is the balanced training dataset that was selected based on its performance classifying the test dataset. We generated 30 balanced datasets then assessed their performance on the test data. This training dataset was selected based on how well it classified hydrated carbonaceous chondrites and iron meteorites. The C parameter for this training dataset is 64. This was obtained by a cross-validation analysis for this balanced dataset where we determined the best overall accuracy for this C parameter.
39+
40+
**Bus-DeMeo-results** - This pickle includes the assigned classification and group probability for each asteroid in the dataset. Can be loaded independently of the BusDeMeoReanalysis.py and used as needed.
41+
42+
### Data files
43+
Folder **DeMeo_csv_files**
44+
The folder containing the Bus-DeMeo asteroid spectra. These data are loaded into a dataframe in the working.py file and classified using the balanced training dataset included in this repo.
45+
46+
**trainTestSplitTesting.xlsx** - This file contains 20% of the spectral dataset (including the original 1422 meteorite spectra from Dyar et al., 2023 as well as the new data). We stratified the dataset (ordering by unnormalized reflectance value at 1.25-µm) and selected every 5th spectrum. These spectra we reserved as a test cohort to validate the accuracy of our classifier.
47+
48+
**trainTestSplitTraining.xlsx** - This file contains 80% of the dataset. These data are used to generate a balanced training dataset then to train the logistic regression classifier.
49+
50+
**Dyar_etal(2023)_dataset.xlsx** - This file is the original 1422 meteorite spectra. It is a duplicate of the supplemental data file from Dyar et al., 2023. Citation: Dyar, M.D., Wallace, S.M., Burbine, T.H. and Sheldon, D.R., 2023. A machine learning classification of meteorite spectra applied to understanding asteroids. Icarus, 406, p.115718.
51+
52+
**new_RELAB_spectra_resampled.xlsx** - This file includes the ~600 new spectra that were put on the RELAB archive after Dyar et al., (2023) created their dataset. These spectra augment the original dataset.
53+
54+
**BD_asteroid_characteristics.xlsx** - This file contains selected orbital and physical parameters of the 371 asteroids that we reanalyze in the paper. These are all publicly available data that were gathered from the JPL Solar System Dynamics Small Bodies search query (ssd.jpl.nasa.gov).
55+
56+
**Bus-DeMeo-results_vfinal.xlsx** - This file contains the physical characteristics as well as the predicted class and probability of each class for all 371 asteroids in the study.

README.md.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Overview:
2+
3+
# Folder 'DeMeo_csv_files' - The folder containing the Bus-DeMeo asteroid spectra. These data are loaded into a dataframe in the working.py file and classified using the balanced training dataset included in this repo.
4+
5+
specmap.py - The Spectral Meteorite Mapping Tool are several functions that create balanced training datasets and use a supervised machine learning approach (logistic regression) to classify data. The logistic regression is trained using the provided balanced dataset and indicated hyperparameter. These functions are used to classify asteroid or meteorite spectra into one of 10 groups based on their spectral similarity.
6+
7+
Working.py - This file is an example of how the software is called and used in python. This file can be localized by other scientists to use and understand our software.
8+
9+
# Pickle files:
10+
11+
balanced_training_dataset2026_01_20_1620 - This file is the balanced training dataset that was selected based on its performance classifying the test dataset. We generated 30 balanced datasets then assessed their performance on the test data. This training dataset was selected based on how well it classified hydrated carbonaceous chondrites and iron meteorites. The C parameter for this training dataset is 64. This was obtained by a cross-validation analysis for this balanced dataset where we determined the best overall accuracy for this C parameter.
12+
13+
Bus-DeMeo-results - This pickle includes the assigned classification and group probability for each asteroid in the dataset. Can be loaded independently of the working.py and used as needed.
14+
15+
BD_asteroid_characteristics.xlsx - This file contains selected orbital and physical parameters of the 371 asteroids that we classify in the paper. These are all publicly available data that were gathered from the JPL Solar System Dynamics Small Bodies search query (ssd.jpl.nasa.gov).
16+
17+
Dyar_etal(2023)_dataset.xlsx - This file is the original 1422 meteorite spectra. It is a duplicate of the supplemental data file from Dyar et al., 2023. Citation: Dyar, M.D., Wallace, S.M., Burbine, T.H. and Sheldon, D.R., 2023. A machine learning classification of meteorite spectra applied to understanding asteroids. Icarus, 406, p.115718.
18+
19+
new_RELAB_spectra_resampled.xlsx - This file includes the ~600 new spectra that were put on the RELAB archive after Dyar et al., (2023) created their dataset. These spectra augment the original dataset.
20+
21+
trainTestSplitTesting.xlsx - This file contains 20% of the spectral dataset (including the original 1422 meteorite spectra from Dyar et al., 2023 as well as the new data). We stratified the dataset (ordering by unnormalized reflectance value at 1.25-µm) and selected every 5th spectrum. These spectra we reserved as a test cohort to validate the accuracy of our classifier.
22+
23+
trainTestSplitTraining.xlsx - This file contains 80% of the dataset. These data are used to generate a balanced training dataset then to train the logistic regression classifier.
24+
25+
Bus-DeMeo-results_vfinal.xlsx - This file contains the physical characteristics as well as the predicted class and probability of each class for all 371 asteroids in the study.
1.76 MB
Binary file not shown.

new_RELAB_spectra_resampled.xlsx

1010 KB
Binary file not shown.

0 commit comments

Comments
 (0)