Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name = "GenomicPro"
uuid = "0c0bde55-41a4-47af-8c3e-91f915c1c200"
authors = ["AI Developer <dev@example.com>"]
version = "0.1.0"

[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
JSON3 = "0f8b85d8-493c-5b1a-9879-9ecda8c0e6a9"

[compat]
CSV = "0.10"
DataFrames = "1"
JSON3 = "1"
julia = "1.12"
42 changes: 42 additions & 0 deletions config/example_pipeline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"datasets": [
{
"name": "transcriptomics",
"path": "data/transcriptomics.csv",
"delimiter": ",",
"feature_column": "Feature",
"normalization": "zscore",
"imputation": "mean",
"metadata": {
"missing_strings": ["NA", ""]
}
},
{
"name": "proteomics",
"path": "data/proteomics.csv",
"delimiter": ",",
"feature_column": "Feature",
"normalization": "zscore",
"imputation": "median"
}
],
"integration": {
"strategy": "concatenate"
},
"analysis": {
"run_pca": true,
"pca_components": 3,
"clustering": "kmeans",
"cluster_count": 3
},
"report": {
"output_path": "reports/pipeline_report.json",
"text_output_path": "reports/pipeline_report.txt"
},
"quality_control": {
"min_samples": 2,
"min_features": 2,
"min_nonmissing_ratio": 0.5,
"min_variance": 1e-8
}
}
80 changes: 79 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1 +1,79 @@
meibujun julia
# GenomicPro

GenomicPro 是一个使用 Julia 语言实现的多组学(multi-omics)分析流水线框架,提供从数据加载、质量控制、归一化、组学数据对齐、集成分析到报告生成的一站式能力。该实现基于 Julia v1.12.1,侧重于易用性与可扩展性,支持 CLI 运行以及通过配置文件管理复杂工作流。

## 关键特性

- **多组学数据加载**:支持从 CSV/TSV 表格读取不同组学层的数据,灵活配置分隔符、特征列、缺失值标记等。
- **缺失值填补与质量控制**:内置均值/中位数/零值填补策略,支持对低方差、缺失率过高的特征进行过滤。
- **标准化与变换**:提供 z-score、min-max、robust scaling 以及可选对数变换,适配不同测序平台的特性。
- **数据对齐与集成**:自动对齐样本,支持特征拼接(concatenate)与加权求和(weighted sum)两种集成策略。
- **统计/机器学习分析**:内置 PCA 降维与 KMeans 聚类,可根据配置决定是否执行并调整参数。
- **报告生成**:输出 JSON 与文本摘要,涵盖数据概览、质量指标、分析结果,方便集成到下游系统。

## 项目结构

```
Project.toml
src/
GenomicPro.jl # 模块入口
types.jl # 核心类型定义
io.jl # 数据读取与配置解析
preprocessing.jl # 缺失值填补、归一化、对齐等预处理
integration.jl # 多组学集成算法
analysis.jl # PCA、KMeans 等分析
reporting.jl # 报告与摘要生成
validation.jl # 数据校验与质量过滤
utils.jl # 通用辅助函数
pipeline.jl # 流水线编排
scripts/
genomicpro_cli.jl # 命令行入口
config/
example_pipeline.json# 配置示例
```

## 安装依赖

```bash
julia --project -e 'using Pkg; Pkg.instantiate()'
```

## 运行示例流水线

准备示例数据(可参考 `test/data` 目录中的 CSV),并在根目录创建输出目录:

```bash
mkdir -p data reports
cp test/data/*.csv data/
```

然后运行命令行脚本:

```bash
julia scripts/genomicpro_cli.jl config/example_pipeline.json
```

执行完成后,JSON 与文本报告分别保存在 `reports/pipeline_report.json` 与 `reports/pipeline_report.txt`。

## 在代码中使用

```julia
using GenomicPro

config = read_pipeline_config("config/example_pipeline.json")
result = run_pipeline(config)
println(render_summary(result))
```

## 测试

```bash
julia --project -e 'using Pkg; Pkg.test()'
```

## 扩展建议

- 接入更多集成策略(如基于网络的融合、协同矩阵分解等)。
- 支持批量效应校正与更丰富的归一化方法。
- 引入更高级的聚类与分类算法,或与 MLJ 生态衔接。
- 构建交互式可视化报告,支持在浏览器中探索多组学结果。
18 changes: 18 additions & 0 deletions scripts/genomicpro_cli.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env julia
using Pkg
Pkg.activate(joinpath(@__DIR__, ".."))
using GenomicPro

function print_usage()
println("Usage: genomicpro_cli.jl <pipeline_config.json>")
end

function main()
isempty(ARGS) && return (print_usage(); exit(1))
config_path = ARGS[1]
config = read_pipeline_config(config_path)
result = run_pipeline(config)
println(render_summary(result))
end

main()
27 changes: 27 additions & 0 deletions src/GenomicPro.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module GenomicPro

export DatasetConfig, IntegrationConfig, AnalysisConfig, ReportConfig, QualityControlConfig,
PipelineConfig, PipelineResult, OmicsDataset, IntegratedDataset,
load_omics_dataset, read_pipeline_config, run_pipeline, align_datasets,
integrate_datasets, normalize!, impute_missing!, filter_low_quality!,
run_pca, run_kmeans, build_report, save_report, render_summary

using CSV
using DataFrames
using JSON3
using LinearAlgebra
using Statistics
using Random
using Dates

include("types.jl")
include("utils.jl")
include("validation.jl")
include("io.jl")
include("preprocessing.jl")
include("integration.jl")
include("analysis.jl")
include("reporting.jl")
include("pipeline.jl")

end
108 changes: 108 additions & 0 deletions src/analysis.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
function run_pca(dataset::IntegratedDataset; components::Int=3)
components > 0 || throw(ArgumentError("Number of components must be positive"))
n_samples = length(dataset.samples)
n_samples > 1 || throw(ArgumentError("PCA requires at least two samples"))
mat = dataset.matrix
centered = mat .- mean(mat, dims=2)
data = transpose(centered)
svd_res = svd(data; full=false)
k = min(components, length(svd_res.S))
scores = svd_res.U[:, 1:k] * Diagonal(svd_res.S[1:k])
loadings = svd_res.V[:, 1:k]
denom = max(n_samples - 1, 1)
explained_variance = (svd_res.S .^ 2) / denom
explained_ratio = explained_variance ./ sum(explained_variance)
return PCAResult(scores, loadings, explained_variance[1:k], explained_ratio[1:k])
end

function _initialise_centroids(data::Matrix{Float64}, k::Int, seed::Int)
Random.seed!(seed)
indices = randperm(size(data, 2))[1:k]
return data[:, indices]
end

function _assign_clusters(data::Matrix{Float64}, centroids::Matrix{Float64})
assignments = Vector{Int}(undef, size(data, 2))
for j in 1:size(data, 2)
column = view(data, :, j)
best_idx = 1
best_dist = Inf
for c in 1:size(centroids, 2)
dist = sum((column .- view(centroids, :, c)).^2)
if dist < best_dist
best_dist = dist
best_idx = c
end
end
assignments[j] = best_idx
end
return assignments
end

function _update_centroids(data::Matrix{Float64}, assignments::Vector{Int}, k::Int)
dim = size(data, 1)
centroids = zeros(Float64, dim, k)
counts = zeros(Int, k)
for j in 1:length(assignments)
cluster = assignments[j]
centroids[:, cluster] .+= view(data, :, j)
counts[cluster] += 1
end
for c in 1:k
if counts[c] > 0
centroids[:, c] ./= counts[c]
end
end
return centroids, counts
end

function _compute_inertia(data::Matrix{Float64}, assignments::Vector{Int}, centroids::Matrix{Float64})
total = 0.0
for j in 1:size(data, 2)
cluster = assignments[j]
diff = view(data, :, j) .- view(centroids, :, cluster)
total += sum(diff .^ 2)
end
return total
end

function run_kmeans(dataset::IntegratedDataset; k::Int=3, maxiter::Int=300, tol::Float64=1e-4, seed::Int=42)
k > 0 || throw(ArgumentError("Number of clusters must be positive"))
data = dataset.matrix
size(data, 2) >= k || throw(ArgumentError("Number of clusters cannot exceed sample count"))
centroids = _initialise_centroids(data, k, seed)
assignments = _assign_clusters(data, centroids)
previous_inertia = Inf
for iter in 1:maxiter
centroids, counts = _update_centroids(data, assignments, k)
for c in 1:k
if counts[c] == 0
centroids[:, c] = data[:, rand(1:size(data, 2))]
end
end
assignments = _assign_clusters(data, centroids)
inertia = _compute_inertia(data, assignments, centroids)
if abs(previous_inertia - inertia) < tol
previous_inertia = inertia
break
end
previous_inertia = inertia
end
return ClusteringResult(assignments, centroids, _compute_inertia(data, assignments, centroids))
end

function run_analysis(dataset::IntegratedDataset, config::AnalysisConfig)
pca_result = nothing
if config.run_pca
pca_result = run_pca(dataset; components=config.pca_components)
end
clustering_result = nothing
if config.clustering === :kmeans
clustering_result = run_kmeans(dataset; k=config.cluster_count, seed=config.random_seed)
elseif config.clustering === nothing || config.clustering === :none
clustering_result = nothing
else
throw(ArgumentError("Unsupported clustering strategy: $(config.clustering)"))
end
return AnalysisResult(pca=pca_result, clustering=clustering_result)
end
44 changes: 44 additions & 0 deletions src/integration.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
function integrate_concatenate(datasets::Vector{OmicsDataset})
combined_matrix = vcat([ds.matrix for ds in datasets]...)
combined_features = String[]
for ds in datasets
append!(combined_features, [string(ds.name, "::", feature) for feature in ds.features])
end
metadata = Dict{String, Any}(
"strategy" => "concatenate",
"source_datasets" => [ds.name for ds in datasets]
)
return IntegratedDataset("Integrated", combined_matrix, combined_features, copy(datasets[1].samples), metadata)
end

function integrate_weighted_sum(datasets::Vector{OmicsDataset}, weights::Dict{String, Float64})
reference_features = datasets[1].features
for ds in datasets[2:end]
ds.features == reference_features || throw(ArgumentError("Weighted sum integration requires identical feature ordering"))
end
weight_vec = [weights[ds.name] for ds in datasets]
weight_sum = sum(weight_vec)
weight_sum == 0 && throw(ArgumentError("Weights must not sum to zero"))
normalised = weight_vec ./ weight_sum
combined_matrix = zeros(Float64, size(datasets[1].matrix))
for (w, ds) in zip(normalised, datasets)
combined_matrix .+= w .* ds.matrix
end
metadata = Dict{String, Any}(
"strategy" => "weighted_sum",
"weights" => Dict(ds.name => weights[ds.name] for ds in datasets)
)
return IntegratedDataset("Integrated", combined_matrix, copy(reference_features), copy(datasets[1].samples), metadata)
end

function integrate_datasets(datasets::Vector{OmicsDataset}, config::IntegrationConfig)
isempty(datasets) && throw(ArgumentError("No datasets provided for integration"))
if config.strategy == :concatenate
return integrate_concatenate(datasets)
elseif config.strategy == :weighted_sum
weights = _ensure_weights(datasets, config.weights)
return integrate_weighted_sum(datasets, weights)
else
throw(ArgumentError("Unsupported integration strategy: $(config.strategy)"))
end
end
Loading