用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/arm2arm/AstroAgentAssistant --skill astronomy-analysis-project命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
S3/MinIO operations: connectivity, transfers, read benchmarks, and matplotlib visualization templates.
Complete guide to the REANA reproducible analytics platform: Dockerized client setup, multi-backend profiles, workflow authoring patterns, S3 dataset workflows, and best practices. Covers dev/prod backends, serial workflows, REANA_WORKSPACE usage, and self-learning from finished workflows.
Complete guide to working with Arepo simulation HDF5 files: structure inspection, unit conversion, radial profiles, slice projections, and dimensionality reduction (UMAP/t-SNE) for clustering analysis.
正在显示 SKILL.md
| name | astronomy-analysis-project |
| description | Build reproducible Parquet astronomy analysis projects. |
| category | data-science |
Class-level guide for building a reproducible astronomy data analysis project: Parquet catalog I/O, quality cuts, reusable plotting functions, and thin Jupyter frontends — matching the StarHORSE / SHBoost analysis workflow.
project/
├── config/storage.yaml # Backend config (local/S3)
├── pyproject.toml
├── requirements.txt
├── README.md
├── .gitignore
├── src/<pkg>/
│ ├── __init__.py
│ ├── dataio.py # Parquet reader, column selection, filters
│ ├── analysis.py # Quality cuts, derived columns, stats
│ └── plots.py # Figure functions (one per plot family)
├── notebooks/ # Thin Jupyter frontends (one per figure)
├── paper/figures/ # Generated PNG+PDF+metadata
├── results/ # CSV summary tables
└── logs/
config/storage.yaml:
storage:
backend: local # "local" | "s3"
local:
base_path: "/path/to/parquet_dataset/"
s3:
endpoint: ""
bucket: ""
region: "us-east-1"
path_prefix: "dataset/"
dataset:
layer: "silver"
Switch backends by changing backend only — all code reads from config.
dataio.py)Use pyarrow.parquet.read_table() with columns and filters params.
Support both single file and directory-style partitioned dataset.
import pyarrow.parquet as pq
def load_catalog(columns=None, config_path="config/storage.yaml"):
config = yaml.safe_load(open(config_path))
path = config["storage"]["local"]["base_path"]
if config["storage"]["backend"] == "s3":
path = f"s3://{config['storage']['s3']['bucket']}/{config['storage']['s3']['path_prefix']}"
table = pq.read_table(path, columns=columns, filters=None)
return table.to_pandas()
analysis.py)Define quality cuts as filter functions:
def filter_quality_cuts(df, config_path="config/storage.yaml"):
mask = pd.Series(True, index=df.index)
if "ruwe" in df.columns:
mask &= df["ruwe"].notna() & (df["ruwe"] <= 1.4)
# parallax SNR, distance range, etc.
return df[mask]
Derived columns (colors, absolute magnitude, uncertainties):
def compute_derived_columns(df):
result = df.copy()
result["g_bp_rp"] = df["phot_bp_mean_mag"] - df["phot_rp_mean_mag"]
# absolute magnitude
result["mg"] = df["phot_g_mean_mag_march2021"] - 5 * np.log10(df["dist50"]) + 5
# uncertainties
result["e_mass"] = (df["mass84"] - df["mass16"]) / 2.0
return result
plots.py)Each plot family as a function returning output path:
def plot_kiel_diagram(df, outdir="paper/figures"):
fig, ax = plt.subplots(figsize=(7, 6))
mask = df["teff50"].notna() & df["logg50"].notna()
ax.hexbin(df.loc[mask, "teff50"], df.loc[mask, "logg50"],
gridsize=100, mincnt=1, cmap="viridis",
norm=mcolors.LogNorm(vmin=1)) # NOT minmax=True
ax.invert_xaxis()
ax.invert_yaxis()
path = Path(outdir) / "kiel_diagram.png"
fig.savefig(path, dpi=300)
return path
Thin notebooks calling the module:
import sys
sys.path.insert(0, "../src")
from starhorse2026.dataio import load_sample
from starhorse2026.plots import plot_kiel_diagram
df = load_sample("quality_cut")
plot_kiel_diagram(df)
Every figure saves:
For larger catalogs (10⁸+ rows) or long-lived multi-paper projects, the flat
plots.py module above has been superseded by a registry pattern:
plots/p01_cmd.py, …), each declaring
SPEC = PlotSpec(id, name, columns=[...], derived=[...], params={...})
make(df, ctx). Auto-discovered by a registry; CLI selects by id/range.dd.read_parquet(columns=…) —
only those columns are ever read from disk. Derived columns (MG0, galactic
coords) are added lazily via map_partitions.n_workers × threads_per_worker
and memory_limit give a hard cap, with spill-to-disk instead of OOM.
Tune memory_limit to the host, not to the dataset — an oversized
per-worker memory_limit does NOT protect you from the OS OOM killer.
On the SH26 local host the standing cap is ~14 GB total
(3 workers × 4.5 GB); per-worker limits above ~7 GB get OOM-killed
(exit code -9) even with spill enabled. Size workers so the total fits
the machine's free RAM, and let Dask spill the rest to disk.See the starhorse-plots skill (references/sh26_dask_framework.md) for the
full working implementation. Use the simple pattern above for quick projects;
graduate to the registry pattern when a project will produce multiple papers
or the catalog outgrows RAM.
int(len(ddf)) materializes the whole catalog: calling len() on a Dask DataFrame triggers a full count compute over every row/partition — on a 50M×128-col joined catalog this can OOM or take minutes for no reason. Only call it on the pruned, column-subset frame the current plot actually needs (and cache the lazy Dask frame so you read the parquet metadata once, not per plot).r_med_geo_bj21, r_med_photogeo_bj21, r_lo_*/r_hi_*) are in parsecs while SH26 dist50 and the SH21/Weiler dist50_* columns are in kpc. Plotting raw BJ21 vs dist50 produces a hexbin squished into the bottom-left corner with an x-axis max ~48,000 "kpc". Divide BJ21 distances by 1000 first. Quick diagnostic: median(dist50 / <col>) ≈ 1 means same unit; ≈ 0.001 means <col> is in parsecs.--cuts), not baked into loading.filters not filter: pq.read_table() takes filters=... (plural), NOT filter=. Using filter raises TypeError: read_table() got an unexpected keyword argument 'filter'.minmax removed: In matplotlib 3.7+, minmax=True and reduce_C_function= are NOT supported on hexbin(). Use norm=LogNorm(vmin=1) instead.cb.ax.set_yticklabels() after LogNorm can mislabel ticks unless you use FixedLocator. Prefer LogFormatterSciNotation from matplotlib.ticker for clean log labels..dropna() on individual columns returns series with DIFFERENT indices. Always use a boolean mask () to align columns before plotting.See matplotlib-pitfalls skill for hexbin log scale, inverted axis, and NaN handling issues.
df[...].notna() & df[...].notna()ParquetDataset may return fewer columns if some parquet files lack certain columns. Use columns=[...] in read_table() and handle missing columns explicitly.config/storage.yaml base_path must be an absolute path — relative paths resolve from CWD which changes between make targets and notebook execution.E(BP-RP)/A_V = 1.33 dereddening factor. The original notebooks use temperature-dependent Gaia EDR3 extinction corrections via photutils.py (coefficients from F. Anders). Use MG0(G_obs, AV, dist, Teff) and BPRP0(BP_obs, RP_obs, AV, Teff) — the AG(AV,Teff), ABP(AV,Teff), ARP(AV,Teff) polynomials — not 1.33 * E(BP-RP). A flat correction will wash out the main sequence turn-off and red clump structure.