seqpro について
バイオインフォマティクスのワークフロー向けの Python/Rust 配列処理パッケージです。DNA、RNA、タンパク質のエンコード、変換、統計、ゲノム区間の入出力に対応し、可変長バッチには Rust ネイティブの Ragged コンテナを使えます。
主な機能
- 01配列のエンコード、デコード、パディング
- 02逆相補、k-mer シャッフル、ジッター
- 03GC 含量と塩基組成の計算
- 04可変長に対応する Rust ネイティブ Ragged 配列
- 05BED と GTF 区間の読み書き
活用シーン
- 01配列を one-hot 配列や整数トークンに変換
- 02DNA、RNA、タンパク質配列を拡張
- 03長さの異なる配列バッチを処理
- 04BED や GTF のゲノム区間を読み書き
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ml4gland/ seqpro --skill seqproインストール後、Claude Code で/seqproを入力して読み込まれたか確認します。
詳細
seqpro
生物学的配列を高速に処理する Python/Rust パッケージです。ホットループには Python+NumPy+Numba、グラフアルゴリズムの処理には小規模な Rust 拡張機能(src/kshuffle.rs)、可変長のバッチには Rust ネイティブの Ragged 配列(_core.Ragged)を使用します。import seqpro as sp でインポートします。
使用する場面
- DNA、RNA、タンパク質配列のエンコード/デコード(OHE、整数トークン、パディング)。
- 配列の拡張:逆相補鎖、k-mer シャッフル、ジッター、ランダムな抽出。
- 配列の統計量:GC 含量、ヌクレオチド組成、長さ。
- 可変長バッチ(例:サイズの異なるピークや転写産物)→
sp.Ragged。 - ゲノム区間の I/O:
sp.bed、sp.gtf。
規約(作業メモリに読み込んでください)
- 公開 API:エクスポートされるシンボルの全一覧は
python/seqpro/__init__.pyを参照してください。シンボルが存在すると決めつける前に、読み直してください。 - 入力型(
python/seqpro/_utils.pyのSeqType):str、bytes、ネストされた str のリスト、または dtype がstr_/object_/bytes_/uint8のndarrayです。sp.cast_seqs(...)は文字列形式の入力を|S1のバイト配列に正規化します。uint8(OHE)は変更されません。 - 標準のメモリ内 dtype:文字列配列には
|S1、one-hot 配列にはuint8を使用します。 - 軸引数は必須で、明示的に指定します:ほとんどの関数では
length_axis、また(OHE の場合は)ohe_axisを位置引数またはキーワード引数の整数として指定します。負のインデックスも使用できます。check_axes()は検証して早期にエラーを送出します。エラーを捕捉して握りつぶさないでください。 - ライブラリコードで配列ごとに Python ループを使用しないでください。 ホットパスでは NumPy、
_numba.pyの Numba カーネル、または Rust 拡張機能を使用します。残基をforループで処理しようとしている場合は、まず既存のベクトル化された処理または Numba カーネルを探してください。 - アルファベットはシングルトンです:
sp.DNA、sp.RNA、sp.AA。カスタムのアルファベットはsp.NucleotideAlphabet/sp.AminoAlphabet(python/seqpro/alphabets/_alphabets.py)で作成します。 AminoAlphabet.translate(seqs, ..., validate=False, unknown="X"):ヌクレオチドをアミノ酸に変換します。大文字と小文字を区別しません。小文字/ソフトマスクされたacgtは常に変換されます。unknown=は非標準コドン({A,C,G,T}以外のすべて)の処理方法を指定します。1 文字(デフォルトは"X")を指定すると、不正なコドンごとに 1 つのマーカーを埋め込みます。リテラルの"drop"を指定すると、不正なコドンを削除してRaggedを返します(長さが異なるため、密な入力の場合も同様です)。validate=Trueは唯一の高速失敗経路です。N/IUPAC/one-hot 以外の入力がある場合、大文字と小文字を区別せずにエラーを送出し、正常に戻った場合は正確な変換を保証します。別個のerrorモードはありません。- 変換処理(
python/seqpro/transforms/)は関数型の処理を呼び出し可能なオブジェクトとしてラップします。データパイプラインでは、インラインのラムダ式ではなくこれらを使用してください。
クイックリファレンス
| タスク | 呼び出し | メモ |
|---|---|---|
| 入力を正規化 | sp.cast_seqs(x) | → ` |
| one-hot エンコード | sp.ohe(x, alphabet, length_axis=-1) | OHE 次元の軸を末尾に追加 |
| OHE をデコード | sp.decode_ohe(x, alphabet, ohe_axis=-1) | |
| トークン化/デトークン化 | sp.tokenize / sp.decode_tokens | 整数 ID。parallel=True/False は並列カーネルを有効/無効にします(デフォルトの None ではサイズに基づいて判定)。 |
| パディング | sp.pad_seqs(x, pad_val, length=...) | |
| 逆相補鎖 | sp.reverse_complement(x, alphabet, length_axis=-1) | str/bytes/OHE に対応 |
| k-mer シャッフル | sp.k_shuffle(x, k, length_axis=-1, seed=...) | Rust の _k_shuffle を呼び出します |
| ジッター | sp.jitter(x, max_jitter, length_axis=-1) | |
| ランダム配列 | sp.random_seqs(shape, alphabet, seed=...) | |
| GC 含量 | sp.gc_content(x, length_axis=-1) | |
| ヌクレオチド含量 | sp.nucleotide_content(x, alphabet, length_axis=-1) | |
| カバレッジのビニング | sp.bin_coverage(arr, bin_width, length_axis) | |
| BED/GTF の I/O | sp.bed.read_bedlike(...)、sp.gtf.read_gtf(...) | polars/pyranges を利用 |
| Ragged 文字列のハッシュ | rag.hash("sha256"|"md5"|"rapidhash") | Rust カーネルを呼び出します。シードは rapidhash の場合のみ指定します |
シグネチャとキーワード引数を正確に確認するには、docstring を直接参照してください(REPL では sp.<fn>? を実行するか、ソースを開いてください。各ファイルは短めです)。
Ragged — 可変長シーケンスのバッチ
sp.Ragged(python/seqpro/rag/_core.py を基盤としています)は、シーケンスの長さが異なるバッチの標準コンテナーです。Rust ネイティブのクラスであり、NDArrayOperatorsMixin を実装しています(ak.Array のサブクラスではありません)。**不規則な次元が 1 つ、または隣接する 2 つ(ネストされた場合。例:(batch, ploidy, None, None))**を持ち、基盤となるフラットな NumPy バッファーとオフセットにゼロコピーでアクセスできます。
概要
Ragged は次の 3 つの要素で構成されます。
data:形状(total_elements, *fixed_trailing_dims)の、フラットで連続したNDArrayです。rag.dataからゼロコピーでアクセスできます。offsets:int64配列です。形状は(N+1,)(連続している一般的なケース)、または(2, N)(一部のスライス後の開始位置/終了位置)です。rag.offsetsからアクセスできます。ネストされた配列では、不規則な軸ごとに 1 つずつ、外側の軸から順に並びます。shape:(batch, None, ohe_dim)のようなタプルです。要素の 1 つがNoneの場合はそこが不規則な軸で、ネストされた配列では隣接する 2 つの要素が該当します。最初の不規則な軸のインデックスはrag.rag_dimで取得できます。
rag.lengths はオフセットからセグメントの長さを導出します(低コストで、ndarray を返します)。
構築
import numpy as np, seqpro as sp
# From lengths (most common — you have a flat numeric data buffer and per-segment lengths)
data = np.frombuffer(b"ACGTACGTACG", dtype=np.uint8) # uint8 for char-as-number
lengths = np.array([4, 3, 4])
rag = sp.rag.Ragged.from_lengths(data, lengths) # shape (3, None)
# From explicit offsets (also accepts S1 char arrays when shape has a None)
offsets = np.array([0, 4, 7, 11], dtype=np.int64)
char_data = np.frombuffer(b"ACGTACGTACG", dtype="S1")
rag = sp.rag.Ragged.from_offsets(char_data, shape=(3, None), offsets=offsets) # shape (3, None)
# Empty with known shape
rag = sp.rag.Ragged.empty((10, None, 4), dtype=np.uint8) # batch of 10 OHE seqs
Ragged.empty(shape, dtype) では、shape に None がちょうど 1 つ必要です。末尾の固定次元(例:OHE 軸)は None の後に指定します。
Ragged の使い方 — こうする、これは避ける
| タスク | こうする | これは避ける |
|---|---|---|
| フラットデータに対する一括数値演算 | rag.data[:] = ... または rag.data.view(...) — ゼロコピーです | for seq in rag: で反復する |
np.ufunc を適用する | そのまま呼び出します:np.exp(rag) — __array_ufunc__(NDArrayOperatorsMixin)経由でディスパッチされ、Ragged を返します | 手動でアンパックして再構築する |
| 最上位の行数を数える | len(rag) — shape[0] を返します(shape[0] が可変長軸の場合は例外が発生します) | rag.shape[0] を手動で整数にキャストする |
不透明文字列の Ragged のグループを1つインデックス指定する | rag[i] → 文字列ごとに1つの bytes を持つ Ragged(len(rag[i]) == rag.lengths[i]);rag[i][j] は1つの bytes です | 連結された1つの bytes を期待する — これは0.22より前の動作で、文字列ごとの境界が暗黙に失われていました |
先頭の軸が固定されたネスト配列(例:(b, p, ~v, ~w))をインデックス指定する | 固定軸にNumPyスタイルのキーを使います:rag[i] → (p, ~v, ~w)、rag[:, j] → (b, ~v, ~w);rag[i, j] == rag[i][j] で (n_v, ~w) まで掘り下げます | 行の倍数を見つけるために rag.offsets を手動で読む |
| 可変長軸または後続軸をインデックス指定する | NumPy/awkwardスタイルのタプルキーを使い、それぞれが次の出力軸に適用されます:rag[:, 0] → 各行の先頭要素(密配列;行が短すぎる場合は IndexError)、rag[:, 1:]/rag[:, -2:] → 行ごとのスライス(ゼロコピー)、(b, ~, K) に対する rag[:, :, k] → 後続列。ネスト配列でも動作します((b, ~v, ~w) に対する rag[:, :, 0] → 各ウィンドウの先頭)。 | 行ごとにループする;ステップ付きスライス、可変長軸での高度なインデックス/マスクキー(2つの可変長軸のうち最初の軸を除く)、レコードに対する後続キーで NotImplementedError が発生することを期待する |
| 先頭にサイズ1の軸を挿入する | rag[np.newaxis] — 形状 (1, *old_shape) の Ragged を返します | from_offsets で手動再構築する |
| バイト/dtypeを再解釈する | rag.view(np.uint8) | np.asarray(rag).view(...)(可変長構造が失われます) |
| 可変長でない軸を形状変更する | rag.reshape(batch, None, k, 4) | rag.data.shape を直接操作する |
| サイズ1の軸を削除する | rag.squeeze(axis)(1次元に縮約される場合は ndarray を返します) | |
| NumPyの密配列に変換する | rag.to_numpy()(allow_missing に応じてパディングまたは例外) | ループしてスタックする |
| 連続バッファーにパックする | rag.to_packed() または sp.rag.to_packed(rag) — Numbaで並列化されており、np.memmap でも安全です;すでにパック済みの場合は copy=False でゼロコピーのまま渡せます | ak.to_packed(rag) |
| 密配列に変換し、右側を固定長までパディングする | sp.rag.to_padded(rag, pad_value, *, length=None) — フラットバッファー用Numbaカーネルです;length=None はバッチ内の最大長までパディングし、明示的な length はパディングまたは切り詰めを行います;可変長軸が最後にある配列のみ、レコードは不可です | 手動スライスを使う rag.to_numpy()、または ak_str.rpad(約3倍遅く、awkwardの経路では余分な中間データが割り当てられます) |
| 可変長軸に沿って連結する | sp.rag.concatenate(rags, axis) — 可変長軸に沿って Ragged 配列のリストを連結します(axis は None 次元である必要があり、負の値も使用できます);Rust/rayonカーネルによるオフセット演算とバッファー経由のコピーを行います;数値dtype(int32、float32、…)に対応します | ak.concatenate(rags, axis=…) |
| 通常のawkward配列に変換する | rag.to_ak() |
レコードレイアウトの Ragged(複数フィールド)
同じオフセットオブジェクトを共有する単一フィールドの Ragged を辞書にまとめ、sp.rag.zip(または同等の Ragged.from_fields)を呼び出して構築します。結果はレコードレイアウトを持つ Ragged です。
import numpy as np, seqpro as sp
from seqpro.rag._utils import lengths_to_offsets
lengths = np.array([4, 3])
shared_offsets = lengths_to_offsets(lengths)
seq_rag = sp.rag.Ragged.from_offsets(seq_flat, shape=(2, None), offsets=shared_offsets) # |S1
score_rag = sp.rag.Ragged.from_offsets(score_flat, shape=(2, None), offsets=shared_offsets) # f4
batch = sp.rag.zip({"seq": seq_rag, "score": score_rag}) # → Ragged (record layout)
# equivalently: batch = sp.rag.Ragged.from_fields({"seq": seq_rag, "score": score_rag})
assert isinstance(batch, sp.rag.Ragged)
batch["score"].data[:] *= 2.0 # zero-copy mutation of the flat score buffer
入力は同じオフセットオブジェクトを共有する必要があります(各 from_offsets 呼び出しに同じ shared_offsets 配列を渡してください)。これにより、結果が単一の Ragged 次元を持つレコードになります。独立して構築した from_lengths の結果を渡すと、長さが同じでもオフセットオブジェクトが異なるため、ValueError が発生します。
rag.dtypeは NumPy の構造化 dtype(例:[("seq","S1"),("score","f4")])を返します。これは記述子にすぎず、メモリ上の配置は AoS ではなく SoA です。rag.dataは単一の配列ではなく、フィールド名をキーとする辞書を返します。インデックスを指定する前に、必ず型を確認してください。rag["field"]はゼロコピーで単一フィールドにアクセスでき、親のオフセットオブジェクトを共有します。その.dataは、そのフィールドのフラットな NumPy バッファーです。- レコードレイアウトでの
rag.to_numpy()は、辞書{field: dense ndarray}を返します(フィールドがジャグ配列のままの場合は例外が発生します。密な変換には長さが一様である必要があります)。 - レコードレイアウトでは
viewとapplyは定義されていません。フィールドごとに操作してください。 batch.to_ak()はak.zipのデフォルトと同様に、レコードを末端に配置します。(2, None)→2 * var * {seq, score}です。0.23 より前は、先行軸が 1 つのレコードは、代わりに各行で結合されていました(depth_limit=1)。- 行を取り出す(整数
iを使ったrag[i])と、各エントリがすべて同じ長さの辞書が返されます。数値/文字フィールドはndarray、不透明文字列フィールドはbytesのRaggedです。これにより、zip(row["start"], row["alt"])が正しく機能します。
文字列のハッシュ化
並列 Rust カーネルを使って、Ragged 内の各文字列(不透明文字列または S1 文字の末端、任意の深さ)をハッシュ化します。
digests = rag.hash("sha256") # (N, 32) uint8, one digest per string
md5s = rag.hash("md5") # (N, 16) uint8
fast = rag.hash("rapidhash") # (N,) uint64
seeded = rag.hash("rapidhash", seed=42) # seed valid for rapidhash only
# equivalently: sp.rag.hash(rag, "sha256")
出力は文字列レベルより上の構造を維持します。文字列がグループ化されていない場合(フラットな (N, …)/先行固定次元)は通常の NumPy 配列になり、グループ化されている場合は外側のオフセットを再利用した Ragged になります(md5/sha256 では (G, None, 16/32)、rapidhash では (G, None) の uint64)。数値型およびレコードレイアウトの Ragged は拒否されます。
NumPy との相互運用 — 信頼できること
_core.Ragged は、awkward への依存なしに NDArrayOperatorsMixin と __array_ufunc__ を直接実装しています。
- 非レコードの
Raggedに対する NumPy ufunc(np.add、np.expなど)はRaggedを返します。レコードレイアウトではNotImplementedErrorが発生するため、個々のフィールドに対して操作してください。 rag.to_packed()/sp.rag.to_packed(rag)は、連続したゼロ起点のバッファーを実体化する標準的な方法です。Numba で並列化されており、np.memmapでも安全に使えます。配列がすでにパック済みの場合にゼロコピーでそのまま返すにはcopy=Falseを使います(パック済みでない場合はValueErrorが発生します)。_core.Raggedで awkward(ak.*)API を使わないでください。バックエンドはak.behaviorを登録しなくなりました。awkward との相互運用が必要な場合はrag.to_ak()でak.Arrayを取得できますが、ネイティブ API の使用を推奨します。
迷った場合は python/seqpro/rag/_core.py を確認してください。これは現在のバックエンドであり、真実の情報源はその docstring です。同じディレクトリ内の _layout.py、_ops.py、_utils.py には、関連する内部実装が含まれています。
よくある落とし穴
- スライス後にオフセットのレイアウトが変わります。
rag.offsetsが(N+1,)ではなく(2, N)の開始位置/終了位置になることがあります。(N+1,)を前提とするコードを実行する前に、rag.is_contiguousを確認するか、rag.to_packed()を呼び出してください。 - レコードレイアウトでの
rag.dataは辞書です。rag.data.shapeのようなコードは失敗します。isinstance(rag.data, dict)で分岐するか、rag.partsを使って確認してください。 RaggedのshapeにはNoneが 1 つ、または隣接するNoneが 2 つ(ネスト型)必要です。 ジャグ配列の構造が一致しないデータから構築すると、__init__で例外が発生します。判断に迷った場合はfrom_lengths/from_offsetsを使ってください。- Rust の k-shuffle は、最後の軸が配列長である連続した
uint8を想定しています。sp.k_shuffleがこの処理を行います。seqpro._k_shuffleを直接呼び出す場合は、レイアウトが適切であることを確認してください。
参照先(暗記せず、ソースを読んでください)
| 確認したい内容 | ファイル |
|---|---|
| 公開インターフェース | python/seqpro/__init__.py |
| 入力のキャスト/軸ヘルパー | python/seqpro/_utils.py |
| OHE/トークン/パディング | python/seqpro/_encoders.py |
| 拡張 | python/seqpro/_modifiers.py |
| 統計 | python/seqpro/_analyzers.py |
| アルファベット | python/seqpro/alphabets/_alphabets.py |
| 可変長データ | python/seqpro/rag/_core.py |
| 変換(パイプラインオブジェクト) | python/seqpro/transforms/ |
| BED/GTF | python/seqpro/bed.py, gtf.py |
| Rust の k-shuffle | src/kshuffle.rs |
| 使用例としてのテスト | tests/ |
| レンダリング済みドキュメント | site/(docs/からビルド) |
やってはいけないこと
- ライブラリコードで、残基や位置を対象に Python の
forループを書かないでください。ベクトル化された演算を探すか、_numba.pyの Numba カーネルを使うか、既存のカーネルを拡張してください。 - 軸を決めつけないでください。必ず
length_axisを(該当する場合はohe_axisも)明示的に渡してください。 - ユーザーコードから
Raggedの内部(_layout、_rl、__init__の近道)にアクセスしないでください。data、offsets、fields、from_lengths、from_offsets、from_fields、emptyを使ってください。 Raggedに文字列を追加しないでください。ASCII バイト(|S1)のみを使ってください。- このスキルを更新せずに機能を追加したり、公開シグネチャを変更したりしないでください。CLAUDE.md を参照してください。