SequenceFeature.kmer_composition

SequenceFeature.kmer_composition(df_seq, k=1, list_parts=None, return_df=False, return_scales=False)[source]

Create the k-mer-composition (k-mer) baseline matrix for given sequences.

Builds the no-positional-split k-mer-composition baseline featurization: for each sequence the requested Parts are concatenated into one span and the fraction of each of the 20 ** k ordered overlapping k-mers of adjacent canonical residues is computed, yielding the (n_seq, 20 ** k) matrix X. Columns are all length-k amino-acid strings in ut.LIST_CANONICAL_AA order (itertools.product order: A, C, ..., Y for k=1; AA, AC, ..., YY for k=2; …) so column order is stable, and each row with at least k canonical residues sums to 1. k=1 is amino-acid composition (AAC, identical to aa_composition()) and k=2 dipeptide composition (DPC, identical to dipeptide_composition()); higher k captures longer local sequential order. Unlike SequenceFeature.feature_matrix(), which averages scales over a specific Part-Split, this method carries no positional information — it is a plain k-mer frequency count.

Application. Use this to build a baseline feature set for a prediction model and compare it against a CPP feature_matrix() (the “composition baseline vs CPP” comparison), quantifying how much the positional Part-Split-Scale features add over a plain k-mer frequency encoding. Larger k encodes more local order at the cost of a 20 ** k-wide, sparser matrix — reach for CPP when you need where-along-the-sequence information.

Compositional descriptors (iFeature parity). These non-positional, fixed-length residue-frequency vectors are the classic composition descriptors of tools such as iFeature [Chen18]. The supported approaches:

  • AAC (amino-acid composition, k=1) — the fraction of each of the 20 amino acids (20-D). The simplest composition; captures which residues, not order.

  • DPC (dipeptide composition, k=2) — the fraction of each ordered adjacent pair (400-D). Adds local sequential order (which residue follows which).

  • k-mer (k >= 3) — the general 20 ** k-D extension to longer motifs; grows exponentially and sparsifies quickly, so it is most reliable at low k.

Other iFeature-style descriptors (CTD — composition/transition/distribution of physicochemical groups, PAAC — pseudo amino-acid composition, DDE, …) are the natural follow-ups to complete the family. To turn any of these into a CPP df_feat (AAC positional; DPC/k-mer non-positional), see CPP.run_composit().

Added in version 1.1.0.

Parameters:
  • df_seq (pd.DataFrame, shape (n_samples, n_seq_info)) – DataFrame containing an entry column with unique protein identifiers and sequence information in a distinct format: Position-based, Part-based, Sequence-based, or Sequence-TMD-based (the same input accepted by SequenceFeature.get_df_parts()).

  • k (int, default=1) – k-mer length (>= 1). 1 gives amino-acid composition (AAC, 20 columns), 2 the dipeptide composition (DPC, 400 columns), 3 tripeptides (8000), 4 tetrapeptides (160000). Capped at 4 because the 20 ** k column count grows exponentially.

  • list_parts (str or list of str, optional) – Sequence Parts concatenated into the span (default 'tmd_jmd', the whole TMD-JMD span), passed to SequenceFeature.get_df_parts().

  • return_df (bool, default=False) – If True, return a labeled pd.DataFrame (rows indexed like df_parts, one column per k-mer) instead of a plain array.

  • return_scales (bool, default=False) – If True, also return the CPP-ready (df_scales, df_cat) for the composition, i.e. the return becomes the 3-tuple (X, df_scales, df_cat). For k=1 (AAC) df_scales is the (20, 20) one-hot identity scale set — pass it with df_cat to CPP.run() (whole-part Segment(1,1) split) to obtain amino-acid composition as a real df_feat / feature map. For k>=2 df_scales is None (a k-mer is not a per-residue scale, so it cannot be a CPP scale) and df_cat categorizes the k-mers by residue class for the composition map’s grouping / labels.

Returns:

X – k-mer-composition matrix: per sequence, the fraction of each of the 20 ** k ordered k-mers over the span residues (columns in itertools.product(ut.LIST_CANONICAL_AA, repeat=k) order). A span with fewer than k canonical residues has an all-NaN row. Returned as a pd.DataFrame (k-mer strings as columns) when return_df=True. When return_scales=True the return is the 3-tuple (X, df_scales, df_cat) (df_scales is None for k>=2).

Return type:

array-like, shape (n_samples, 20 ** k)

See also

Examples

SequenceFeature.kmer_composition builds a k-mer-composition baseline: for each sequence it concatenates the selected Parts into one span and returns the fraction of every ordered overlapping k-mer of adjacent canonical residues, a (n_seq, 20 ** k) matrix X. It carries no positional information (unlike :meth:CPP.run / :meth:feature_matrix), so it is the natural baseline to compare CPP against.

k selects the composition: k=1 is amino-acid composition (AAC, 20 columns, identical to :meth:aa_composition), k=2 dipeptide composition (DPC, 400 columns, identical to :meth:dipeptide_composition), and higher k captures longer local sequential order.

import numpy as np
import pandas as pd
import aaanalysis as aa
aa.options["verbose"] = False

# Six short sequences (whole sequence used as the span; no flanks needed)
df_seq = pd.DataFrame({
    "entry": [f"P{i}" for i in range(6)],
    "sequence": ["ACDEFGHIKLMNPQRSTVWY", "MKKLLAACDE", "ACACACAC",
                 "WYWYWYWY", "GGGGSSSS", "LLKKLLKK"],
})
sf = aa.SequenceFeature()
aa.display_df(df_seq, n_rows=10, show_shape=True)
DataFrame shape: (6, 2)
  entry sequence
1 P0 ACDEFGHIKLMNPQRSTVWY
2 P1 MKKLLAACDE
3 P2 ACACACAC
4 P3 WYWYWYWY
5 P4 GGGGSSSS
6 P5 LLKKLLKK

``k`` — the composition order. k=1 gives AAC (20 columns): the fraction of each amino acid.

X_aac = sf.kmer_composition(df_seq=df_seq, k=1, return_df=True)
print(f"Shape: {X_aac.shape}")
aa.display_df(X_aac.round(3), n_rows=10, show_shape=True)
Shape: (6, 20)
DataFrame shape: (6, 20)
  A C D E F G H I K L M N P Q R S T V W Y
entry                                        
P0 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000 0.050000
P1 0.200000 0.100000 0.100000 0.100000 0.000000 0.000000 0.000000 0.000000 0.200000 0.200000 0.100000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
P2 0.500000 0.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
P3 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.500000
P4 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000 0.000000 0.000000
P5 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000

k=2 gives DPC (400 columns): the fraction of each ordered adjacent residue pair AA, AC, ..., YY.

X_dpc = sf.kmer_composition(df_seq=df_seq, k=2, return_df=True)
print(f"Shape: {X_dpc.shape}")
# Show only the non-zero dipeptide columns for readability
nz = X_dpc.loc[:, (X_dpc.fillna(0) != 0).any(axis=0)]
aa.display_df(nz.round(3), n_rows=10, show_shape=True)
Shape: (6, 400)
DataFrame shape: (6, 30)
  AA AC CA CD DE EF FG GG GH GS HI IK KK KL LA LK LL LM MK MN NP PQ QR RS SS ST TV VW WY YW
entry                                                            
P0 0.000000 0.053000 0.000000 0.053000 0.053000 0.053000 0.053000 0.000000 0.053000 0.000000 0.053000 0.053000 0.000000 0.053000 0.000000 0.000000 0.000000 0.053000 0.000000 0.053000 0.053000 0.053000 0.053000 0.053000 0.000000 0.053000 0.053000 0.053000 0.053000 0.000000
P1 0.111000 0.111000 0.000000 0.111000 0.111000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.111000 0.111000 0.111000 0.000000 0.111000 0.000000 0.111000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
P2 0.000000 0.571000 0.429000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
P3 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.571000 0.429000
P4 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.429000 0.000000 0.143000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.429000 0.000000 0.000000 0.000000 0.000000 0.000000
P5 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.286000 0.143000 0.000000 0.286000 0.286000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000

k=3 gives tripeptide composition (8000 columns) — longer local order at the cost of a much wider, sparser matrix. k is capped at 4 (20 ** k grows exponentially).

X_k3 = sf.kmer_composition(df_seq=df_seq, k=3)
print(f"k=3 shape: {X_k3.shape}  (20**3 = {20**3} columns)")
print(f"Each row sums to 1 (spans with >= 3 residues): "
      f"{np.allclose(np.nansum(X_k3, axis=1), 1.0)}")
k=3 shape: (6, 8000)  (20**3 = 8000 columns)
Each row sums to 1 (spans with >= 3 residues): True

``k=1`` and ``k=2`` are exactly the named baselines. kmer_composition(k=1) equals :meth:aa_composition and kmer_composition(k=2) equals :meth:dipeptide_composition — the named methods are convenience aliases for those two orders.

same_aac = np.array_equal(sf.kmer_composition(df_seq=df_seq, k=1),
                          sf.aa_composition(df_seq=df_seq), equal_nan=True)
same_dpc = np.array_equal(sf.kmer_composition(df_seq=df_seq, k=2),
                          sf.dipeptide_composition(df_seq=df_seq), equal_nan=True)
print(f"k=1 == aa_composition: {same_aac}")
print(f"k=2 == dipeptide_composition: {same_dpc}")
k=1 == aa_composition: True
k=2 == dipeptide_composition: True

``list_parts`` — which span to count over. By default the whole tmd_jmd span is used; pass specific Parts (needs TMD/JMD columns) to restrict the composition to, e.g., the TMD only.

df_seq_tmd = df_seq.assign(tmd_start=2, tmd_stop=7)  # TMD within the shortest sequence (len 8)
X_tmd = sf.kmer_composition(df_seq=df_seq_tmd, k=1, list_parts="tmd", return_df=True)
aa.display_df(X_tmd.round(3), n_rows=10, show_shape=True)
DataFrame shape: (6, 20)
  A C D E F G H I K L M N P Q R S T V W Y
entry                                        
P0 0.000000 0.167000 0.167000 0.167000 0.167000 0.167000 0.167000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
P1 0.333000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.333000 0.333000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
P2 0.500000 0.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
P3 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.500000
P4 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.000000 0.000000 0.000000 0.000000
P5 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.500000 0.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000

``return_df``. With return_df=False (default) a plain np.ndarray is returned for direct use as a model matrix X; with return_df=True a labeled DataFrame (one column per k-mer) is returned. A span with fewer than k canonical residues has an all-NaN row.

X_arr = sf.kmer_composition(df_seq=df_seq, k=2)
print(type(X_arr).__name__, X_arr.shape, X_arr.dtype)
ndarray (6, 400) float64

Getting CPP-ready scales / categories (``return_scales``). For the feature map you need a scale set + a category table, not the composition matrix X. return_scales=True makes the return the 3-tuple (X, df_scales, df_cat). For k=1 df_scales is the 20×20 one-hot identity — feed it with df_cat to aa.CPP(df_parts, df_scales=df_scales, df_cat=df_cat, split_kws=<Segment(1,1)>).run(...) to obtain amino-acid composition as a real df_feat / feature map. For k>=2 df_scales is None (a k-mer is not a per-residue scale) and df_cat labels each k-mer by its residue class.

X, df_scales, df_cat = sf.kmer_composition(df_seq=df_seq, k=1, return_scales=True)
print(f"k=1 df_scales (one-hot identity): {df_scales.shape}")
aa.display_df(df_cat, n_rows=10, show_shape=True)
k=1 df_scales (one-hot identity): (20, 20)
DataFrame shape: (20, 5)
  scale_id category subcategory scale_name scale_description
1 A Nonpolar Nonpolar A Amino acid A indicator
2 C Polar Polar C Amino acid C indicator
3 D Negative Negative D Amino acid D indicator
4 E Negative Negative E Amino acid E indicator
5 F Aromatic Aromatic F Amino acid F indicator
6 G Nonpolar Nonpolar G Amino acid G indicator
7 H Positive Positive H Amino acid H indicator
8 I Nonpolar Nonpolar I Amino acid I indicator
9 K Positive Positive K Amino acid K indicator
10 L Nonpolar Nonpolar L Amino acid L indicator