dataframe-toolkit 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataframe_toolkit-0.4.1.dist-info/METADATA +230 -0
- dataframe_toolkit-0.4.1.dist-info/RECORD +42 -0
- dataframe_toolkit-0.4.1.dist-info/WHEEL +4 -0
- dataframe_toolkit-0.4.1.dist-info/entry_points.txt +3 -0
- dftk/__init__.py +4 -0
- dftk/cli.py +72 -0
- dftk/commands/__init__.py +100 -0
- dftk/commands/annotate_cmd.py +165 -0
- dftk/commands/base.py +85 -0
- dftk/commands/binx_cmd.py +130 -0
- dftk/commands/clean_cmd.py +51 -0
- dftk/commands/concat_cmd.py +165 -0
- dftk/commands/corr_cmd.py +281 -0
- dftk/commands/dataset_cmd.py +181 -0
- dftk/commands/describe_cmd.py +360 -0
- dftk/commands/eval_cmd.py +657 -0
- dftk/commands/fit_cmd.py +265 -0
- dftk/commands/func_cmd.py +186 -0
- dftk/commands/help.py +63 -0
- dftk/commands/hist_cmd.py +321 -0
- dftk/commands/interp_cmd.py +271 -0
- dftk/commands/line_cmd.py +320 -0
- dftk/commands/melt_cmd.py +78 -0
- dftk/commands/merge_cmd.py +373 -0
- dftk/commands/pivot_cmd.py +438 -0
- dftk/commands/print_cmd.py +36 -0
- dftk/commands/query_cmd.py +230 -0
- dftk/commands/randvar_cmd.py +199 -0
- dftk/commands/sample_cmd.py +126 -0
- dftk/commands/scale_cmd.py +299 -0
- dftk/commands/scat_cmd.py +278 -0
- dftk/commands/split_cmd.py +130 -0
- dftk/commands/stat_cmd.py +289 -0
- dftk/commands/test_cmd.py +235 -0
- dftk/commands/wstat_cmd.py +163 -0
- dftk/common/__init__.py +8 -0
- dftk/common/io.py +690 -0
- dftk/common/plot.py +566 -0
- dftk/common/seed.py +25 -0
- dftk/common/seq.py +150 -0
- dftk/common/stats.py +214 -0
- dftk/io.py +10 -0
dftk/common/seq.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.common.seq — DNA/sequence utility functions.
|
|
3
|
+
|
|
4
|
+
Row-wise functions for use with pandas .apply(), importable by any command.
|
|
5
|
+
|
|
6
|
+
Available functions
|
|
7
|
+
-------------------
|
|
8
|
+
gc_content(x) GC percentage of a nucleotide string.
|
|
9
|
+
letter_count(x) Tuple (A, T, G, C) counts.
|
|
10
|
+
letter_total(x) Total ATGC count (non-ATGC characters excluded).
|
|
11
|
+
at_gc_ratio(x) (A+T) / (G+C) ratio.
|
|
12
|
+
count_motif(pattern, seq) Count (possibly overlapping) occurrences of a
|
|
13
|
+
nucleotide pattern; N in the pattern matches any base.
|
|
14
|
+
read_offset(read_pos, ref_pos, cigar, read_seq, count)
|
|
15
|
+
Extract bases from a short read at a given reference
|
|
16
|
+
position, accounting for CIGAR-encoded alignment gaps.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"gc_content",
|
|
25
|
+
"letter_count",
|
|
26
|
+
"letter_total",
|
|
27
|
+
"at_gc_ratio",
|
|
28
|
+
"count_motif",
|
|
29
|
+
"read_offset",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Base composition
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def letter_count(x: str) -> tuple[int, int, int, int]:
|
|
39
|
+
"""Return (A, T, G, C) counts for nucleotide string *x*."""
|
|
40
|
+
letters = list(str(x).upper())
|
|
41
|
+
return (
|
|
42
|
+
letters.count("A"),
|
|
43
|
+
letters.count("T"),
|
|
44
|
+
letters.count("G"),
|
|
45
|
+
letters.count("C"),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def gc_content(x: str) -> float:
|
|
50
|
+
"""GC percentage of nucleotide string *x*.
|
|
51
|
+
|
|
52
|
+
Returns NaN for strings that contain no ATGC characters.
|
|
53
|
+
"""
|
|
54
|
+
A, T, G, C = letter_count(x)
|
|
55
|
+
total = A + T + G + C
|
|
56
|
+
return 100.0 * (G + C) / total if total > 0 else np.nan
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def letter_total(x: str) -> int:
|
|
60
|
+
"""Total ATGC character count in *x*."""
|
|
61
|
+
A, T, G, C = letter_count(x)
|
|
62
|
+
return A + T + G + C
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def at_gc_ratio(x: str) -> float:
|
|
66
|
+
"""(A+T) / (G+C) ratio of *x*.
|
|
67
|
+
|
|
68
|
+
Returns NaN when G+C == 0.
|
|
69
|
+
"""
|
|
70
|
+
A, T, G, C = letter_count(x)
|
|
71
|
+
gc = G + C
|
|
72
|
+
return (A + T) / gc if gc > 0 else np.nan
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Motif counting
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def count_motif(pattern: str, seq: str) -> int:
|
|
81
|
+
"""Count occurrences of *pattern* in *seq*.
|
|
82
|
+
|
|
83
|
+
'N' in *pattern* matches any single character (A, T, G, or C).
|
|
84
|
+
Overlapping matches are counted.
|
|
85
|
+
"""
|
|
86
|
+
regex = pattern.replace("N", "(A|T|C|G)")
|
|
87
|
+
return len(re.findall(regex, str(seq)))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# CIGAR-based read offset
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def read_offset(
|
|
96
|
+
read_pos: int,
|
|
97
|
+
ref_pos: int,
|
|
98
|
+
cigar: str,
|
|
99
|
+
read_seq: str,
|
|
100
|
+
count: int = 1,
|
|
101
|
+
) -> str:
|
|
102
|
+
"""Extract *count* bases from a short read at reference position *ref_pos*.
|
|
103
|
+
|
|
104
|
+
Handles M (match/mismatch), I (insertion), D/N (deletion/skip), and
|
|
105
|
+
S (soft-clip) CIGAR operations. Returns '.' for positions outside the
|
|
106
|
+
aligned region and 'N' for positions inside a deletion.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
read_pos: leftmost mapping position of the read on the reference (0-based)
|
|
111
|
+
ref_pos: reference position to extract (0-based)
|
|
112
|
+
cigar: CIGAR string (e.g. "5S40M2I10M")
|
|
113
|
+
read_seq: read sequence string
|
|
114
|
+
count: number of consecutive reference bases to extract
|
|
115
|
+
"""
|
|
116
|
+
cigar_re = re.compile(r"(\d+)([A-Z])")
|
|
117
|
+
offset_list: list[int] = []
|
|
118
|
+
last = -1
|
|
119
|
+
got_soft_clip = False
|
|
120
|
+
|
|
121
|
+
for steps_str, op in cigar_re.findall(cigar):
|
|
122
|
+
steps = int(steps_str)
|
|
123
|
+
if op == "M":
|
|
124
|
+
offset_list += [last + i + 1 for i in range(steps)]
|
|
125
|
+
last = offset_list[-1]
|
|
126
|
+
elif op == "I":
|
|
127
|
+
offset_list += [-1] * steps # insertions consume read but not reference
|
|
128
|
+
elif op == "S" and not got_soft_clip:
|
|
129
|
+
offset_list += [-1] * steps
|
|
130
|
+
last = offset_list[-1]
|
|
131
|
+
got_soft_clip = True
|
|
132
|
+
elif op in ("D", "N"):
|
|
133
|
+
last += steps
|
|
134
|
+
|
|
135
|
+
ref_pos_list = [x + read_pos if x >= 0 else x for x in offset_list]
|
|
136
|
+
|
|
137
|
+
bases: list[str] = []
|
|
138
|
+
for i in range(count):
|
|
139
|
+
target = ref_pos + i
|
|
140
|
+
try:
|
|
141
|
+
idx = ref_pos_list.index(target)
|
|
142
|
+
base = read_seq[idx]
|
|
143
|
+
except ValueError:
|
|
144
|
+
if ref_pos_list and ref_pos_list[0] < target < ref_pos_list[-1]:
|
|
145
|
+
base = "N"
|
|
146
|
+
else:
|
|
147
|
+
base = "."
|
|
148
|
+
bases.append(base)
|
|
149
|
+
|
|
150
|
+
return "".join(bases)
|
dftk/common/stats.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dftk.common.stats — reusable row-wise statistical functions.
|
|
3
|
+
|
|
4
|
+
All functions here take a row (array-like indexed by column name) and return
|
|
5
|
+
a scalar. They are intended to be used as pandas .apply(func, axis=1) targets
|
|
6
|
+
via the dftk eval command, but are importable by any other command.
|
|
7
|
+
|
|
8
|
+
Available functions
|
|
9
|
+
-------------------
|
|
10
|
+
pval2se(row) Convert p-value and effect estimate to standard error.
|
|
11
|
+
t2pval(row) Two-sided p-value from t-statistic and degrees of freedom.
|
|
12
|
+
chi2_to_neglogp(row) Chi-squared + df → -log10(p), numerically stable.
|
|
13
|
+
neglogp_to_chi2(row) -log10(p) + df → chi-squared, numerically stable.
|
|
14
|
+
pval_to_chi2(row) p-value + df → chi-squared.
|
|
15
|
+
binom_test(row) Binomial test p-value.
|
|
16
|
+
fisher_test(row) Fisher exact test p-value (2×2 table).
|
|
17
|
+
fisher_OR(row) Fisher exact test odds ratio (2×2 table).
|
|
18
|
+
boschloo_test(row) Boschloo exact test p-value (2×2 table).
|
|
19
|
+
boschloo_OR(row) Odds ratio from Boschloo test (2×2 table).
|
|
20
|
+
generalized_poisson_nll(row) Negative log-likelihood for the Generalised Poisson.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
import scipy.stats as ss
|
|
25
|
+
from scipy.special import gammaln
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"pval2se",
|
|
29
|
+
"t2pval",
|
|
30
|
+
"chi2_to_neglogp",
|
|
31
|
+
"neglogp_to_chi2",
|
|
32
|
+
"pval_to_chi2",
|
|
33
|
+
"binom_test",
|
|
34
|
+
"fisher_test",
|
|
35
|
+
"fisher_OR",
|
|
36
|
+
"boschloo_test",
|
|
37
|
+
"boschloo_OR",
|
|
38
|
+
"generalized_poisson_nll",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# p-value / chi-squared conversions
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def pval2se(row) -> float:
|
|
47
|
+
"""Convert an effect estimate and its p-value to a standard error.
|
|
48
|
+
|
|
49
|
+
row[0]: effect estimate (beta / log-OR / etc.)
|
|
50
|
+
row[1]: two-sided p-value
|
|
51
|
+
"""
|
|
52
|
+
return float(np.abs(row[0] / ss.norm.ppf(row[1] / 2)))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def t2pval(row) -> float:
|
|
56
|
+
"""Two-sided p-value from a t-statistic and degrees of freedom.
|
|
57
|
+
|
|
58
|
+
row[0]: t-statistic
|
|
59
|
+
row[1]: degrees of freedom
|
|
60
|
+
"""
|
|
61
|
+
return float(2 * ss.t.sf(np.abs(row[0]), row[1]))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def chi2_to_neglogp(row, threshold: float = 1380) -> float:
|
|
65
|
+
"""Convert a chi-squared statistic and df to -log10(p).
|
|
66
|
+
|
|
67
|
+
Uses scipy.stats.chi2.logsf for moderate values; switches to an
|
|
68
|
+
asymptotic approximation above *threshold* to avoid underflow.
|
|
69
|
+
|
|
70
|
+
row[0]: chi-squared statistic
|
|
71
|
+
row[1]: degrees of freedom
|
|
72
|
+
"""
|
|
73
|
+
chi_val = float(row[0])
|
|
74
|
+
df = float(row[1])
|
|
75
|
+
if chi_val < threshold:
|
|
76
|
+
log_p = ss.chi2.logsf(chi_val, df)
|
|
77
|
+
return float(-log_p / np.log(10))
|
|
78
|
+
else:
|
|
79
|
+
lin_term = chi_val / 2.0
|
|
80
|
+
log_term = -(df / 2 - 1) * np.log(chi_val / 2)
|
|
81
|
+
constant = gammaln(df / 2)
|
|
82
|
+
return float((lin_term + log_term + constant) / np.log(10))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def neglogp_to_chi2(
|
|
86
|
+
row, threshold: float = 300, max_iter: int = 50, tol: float = 1e-8
|
|
87
|
+
) -> float:
|
|
88
|
+
"""Convert -log10(p) and df to a chi-squared statistic.
|
|
89
|
+
|
|
90
|
+
Uses scipy.stats.chi2.isf for moderate -log10(p) values; switches to a
|
|
91
|
+
Newton-Raphson inversion of the asymptotic tail formula above *threshold*.
|
|
92
|
+
Closed-form approximation is used for df=2.
|
|
93
|
+
|
|
94
|
+
row[0]: -log10(p)
|
|
95
|
+
row[1]: degrees of freedom
|
|
96
|
+
"""
|
|
97
|
+
neglogp = float(row[0])
|
|
98
|
+
df = int(row[1])
|
|
99
|
+
|
|
100
|
+
if neglogp < threshold:
|
|
101
|
+
p = 10 ** (-neglogp)
|
|
102
|
+
return float(ss.chi2.isf(p, df))
|
|
103
|
+
|
|
104
|
+
constant = gammaln(df / 2)
|
|
105
|
+
nlp = neglogp * np.log(10)
|
|
106
|
+
a = df / 2 - 1
|
|
107
|
+
x = 2 * (nlp - constant)
|
|
108
|
+
if df == 2:
|
|
109
|
+
return float(x)
|
|
110
|
+
for _ in range(max_iter):
|
|
111
|
+
f = nlp - constant + a * np.log(x / 2) - x / 2
|
|
112
|
+
fprime = a / x - 0.5
|
|
113
|
+
step = f / fprime
|
|
114
|
+
x_new = x - step
|
|
115
|
+
if abs(step) < tol:
|
|
116
|
+
return float(x_new)
|
|
117
|
+
x = x_new
|
|
118
|
+
return float(x)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def pval_to_chi2(row) -> float:
|
|
122
|
+
"""Convert a p-value and df to a chi-squared statistic.
|
|
123
|
+
|
|
124
|
+
row[0]: p-value
|
|
125
|
+
row[1]: degrees of freedom
|
|
126
|
+
"""
|
|
127
|
+
return float(ss.chi2.isf(float(row[0]), df=int(row[1])))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# Exact tests
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def binom_test(row) -> float:
|
|
136
|
+
"""Binomial test p-value.
|
|
137
|
+
|
|
138
|
+
row[0]: number of successes (int)
|
|
139
|
+
row[1]: number of trials (int)
|
|
140
|
+
row[2]: hypothesised success probability
|
|
141
|
+
"""
|
|
142
|
+
return float(ss.binomtest(int(row[0]), int(row[1]), float(row[2])).pvalue)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def fisher_test(row) -> float:
|
|
146
|
+
"""Fisher exact test p-value for a 2×2 contingency table.
|
|
147
|
+
|
|
148
|
+
row[0..3]: a, b, c, d (cells of the table, row-major)
|
|
149
|
+
"""
|
|
150
|
+
return float(ss.fisher_exact([[row[0], row[1]], [row[2], row[3]]])[1])
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def fisher_OR(row) -> float:
|
|
154
|
+
"""Fisher exact test odds ratio for a 2×2 contingency table.
|
|
155
|
+
|
|
156
|
+
row[0..3]: a, b, c, d (cells of the table, row-major)
|
|
157
|
+
"""
|
|
158
|
+
return float(ss.fisher_exact([[row[0], row[1]], [row[2], row[3]]])[0])
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def boschloo_test(row) -> float:
|
|
162
|
+
"""Boschloo exact test p-value for a 2×2 contingency table.
|
|
163
|
+
|
|
164
|
+
Boschloo's test is uniformly more powerful than Fisher's exact test for
|
|
165
|
+
testing independence when both margins are fixed.
|
|
166
|
+
|
|
167
|
+
row[0..3]: a, b, c, d (cells of the table, row-major)
|
|
168
|
+
"""
|
|
169
|
+
table = [[int(row[0]), int(row[1])], [int(row[2]), int(row[3])]]
|
|
170
|
+
return float(ss.boschloo_exact(table).pvalue)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def boschloo_OR(row) -> float:
|
|
174
|
+
"""Odds ratio from a 2×2 contingency table (same as Fisher OR).
|
|
175
|
+
|
|
176
|
+
Boschloo's test does not produce an OR directly; we return the
|
|
177
|
+
standard maximum-likelihood OR (ad/bc) instead.
|
|
178
|
+
|
|
179
|
+
row[0..3]: a, b, c, d (cells of the table, row-major)
|
|
180
|
+
"""
|
|
181
|
+
a, b, c, d = float(row[0]), float(row[1]), float(row[2]), float(row[3])
|
|
182
|
+
denom = b * c
|
|
183
|
+
if denom == 0:
|
|
184
|
+
return float("inf") if a * d > 0 else float("nan")
|
|
185
|
+
return float((a * d) / denom)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
# Probability distributions
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def generalized_poisson_nll(row) -> float:
|
|
194
|
+
"""Negative log-likelihood for the Generalised Poisson distribution.
|
|
195
|
+
|
|
196
|
+
Vectorised-safe: operates on scalar values extracted from a row.
|
|
197
|
+
|
|
198
|
+
row[0]: observed count x
|
|
199
|
+
row[1]: lambda parameter
|
|
200
|
+
row[2]: theta parameter
|
|
201
|
+
|
|
202
|
+
Returns -log P(x | lambda, theta), or +inf when lambda + theta*x ≤ 0.
|
|
203
|
+
|
|
204
|
+
log P = log(lambda) + (x-1)*log(lambda + theta*x)
|
|
205
|
+
- (lambda + theta*x) - log(x!)
|
|
206
|
+
"""
|
|
207
|
+
x = float(row[0])
|
|
208
|
+
lam = float(row[1])
|
|
209
|
+
theta = float(row[2])
|
|
210
|
+
lam_theta_x = lam + theta * x
|
|
211
|
+
if lam_theta_x <= 0 or lam <= 0:
|
|
212
|
+
return float("inf")
|
|
213
|
+
log_pmf = np.log(lam) + (x - 1) * np.log(lam_theta_x) - lam_theta_x - gammaln(x + 1)
|
|
214
|
+
return float(-log_pmf)
|
dftk/io.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# This module has moved to dftk.common.io.
|
|
2
|
+
# This file exists only as a compatibility shim and can be deleted once
|
|
3
|
+
# nothing imports from dftk.io directly.
|
|
4
|
+
from dftk.common.io import ( # noqa: F401
|
|
5
|
+
DFTK_TMPDIR,
|
|
6
|
+
_is_pipe_path,
|
|
7
|
+
_write_pipe_parquet,
|
|
8
|
+
globnames,
|
|
9
|
+
io,
|
|
10
|
+
)
|