Skip to content

Commit e4529b3

Browse files
committed
refactor: 换用 psych-lib 进行T检验
1 parent 601d927 commit e4529b3

File tree

7 files changed

+196
-204
lines changed

7 files changed

+196
-204
lines changed

bun.lockb

0 Bytes
Binary file not shown.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"hyparquet": "^1.5.0",
4343
"jieba-wasm": "^2.2.0",
4444
"ml-kmeans": "^6.0.0",
45-
"psych-lib": "^3.0.0",
45+
"psych-lib": "^3.1.1",
4646
"react": "rc",
4747
"react-dom": "rc",
4848
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",

src/lib/utils.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,6 @@ export function generatePResult(statistic: unknown, p: unknown): { statistic: st
2424
}
2525
}
2626

27-
/**
28-
* 计算独立样本 T 检验的 Cohen's d
29-
* @param mean1 样本 1 均值
30-
* @param mean2 样本 2 均值
31-
* @param sd1 样本 1 标准差
32-
* @param sd2 样本 2 标准差
33-
* @param df1 样本 1 自由度
34-
* @param df2 样本 2 自由度
35-
* @returns Cohen's d
36-
*/
37-
export function getCohenDOfTTest2(
38-
mean1: number,
39-
mean2: number,
40-
sd1: number,
41-
sd2: number,
42-
df1: number,
43-
df2: number,
44-
): number {
45-
const top1 = (df1 - 1) * (sd1 ** 2)
46-
const top2 = (df2 - 1) * (sd2 ** 2)
47-
const bottom = df1 + df2
48-
const pooled = Math.sqrt((top1 + top2) / bottom)
49-
return (mean1 - mean2) / pooled
50-
}
51-
5227
/**
5328
* 把当前 echarts 图表保存为图片
5429
*/

src/statistics/OneSampleTTest.tsx

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import { useZustand } from '../lib/useZustand'
2-
import { Select, Input, Button, Form } from 'antd'
2+
import { Select, Input, Button, Form, InputNumber, Space } from 'antd'
33
import { useState } from 'react'
4-
import ttest from '@stdlib/stats/ttest'
54
import { flushSync } from 'react-dom'
65
import { generatePResult } from '../lib/utils'
7-
import { std } from 'psych-lib'
6+
import { OneSampleTTest as T } from 'psych-lib'
87

98
type Option = {
109
/** 变量名 */
1110
variable: string
1211
/** 检验值, 默认 0 */
1312
expect: number
14-
/** 单双尾检验, 默认 two-sided */
15-
alternative: 'two-sided' | 'less' | 'greater'
13+
/** 单双尾检验, 默认双尾 */
14+
twoside: boolean
15+
/** 显著性水平, 默认 0.05 */
16+
alpha: number
1617
}
1718
type Result = {
18-
[key: string]: unknown
19+
m: T
1920
} & Option
2021

2122
export function OneSampleTTest() {
@@ -27,17 +28,12 @@ export function OneSampleTTest() {
2728
try {
2829
messageApi?.loading('正在处理数据...')
2930
const timestamp = Date.now()
31+
const { variable, expect, twoside, alpha } = values
3032
const data = dataRows
31-
.map((row) => row[values.variable])
33+
.map((row) => row[variable])
3234
.filter((v) => typeof v !== 'undefined' && !isNaN(Number(v)))
3335
.map((v) => Number(v))
34-
const result = ttest(data, { mu: +values.expect, alternative: values.alternative })
35-
setResult({
36-
variable: values.variable,
37-
expect: +values.expect,
38-
std: std(data),
39-
...result
40-
} as Result)
36+
setResult({ ...values, m: new T(data, expect, twoside, alpha) })
4137
messageApi?.destroy()
4238
messageApi?.success(`数据处理完成, 用时 ${Date.now() - timestamp} 毫秒`)
4339
} catch (error) {
@@ -62,7 +58,8 @@ export function OneSampleTTest() {
6258
autoComplete='off'
6359
initialValues={{
6460
expect: 0,
65-
alternative: 'two-sided',
61+
twoside: true,
62+
alpha: 0.05,
6663
}}
6764
disabled={disabled}
6865
>
@@ -93,19 +90,35 @@ export function OneSampleTTest() {
9390
type='number'
9491
/>
9592
</Form.Item>
96-
<Form.Item
97-
label='单双尾检验'
98-
name='alternative'
99-
rules={[{ required: true, message: '请选择单双尾检验' }]}
100-
>
101-
<Select
102-
className='w-full'
103-
placeholder='请选择单双尾检验'
104-
>
105-
<Select.Option value='two-sided'>双尾检验</Select.Option>
106-
<Select.Option value='less'>单尾检验(左)</Select.Option>
107-
<Select.Option value='greater'>单尾检验(右)</Select.Option>
108-
</Select>
93+
<Form.Item label='单双尾检验和显著性水平'>
94+
<Space.Compact block>
95+
<Form.Item
96+
noStyle
97+
name='twoside'
98+
rules={[{ required: true, message: '请选择单双尾检验' }]}
99+
>
100+
<Select
101+
className='w-full'
102+
placeholder='请选择单双尾检验'
103+
>
104+
<Select.Option value={true}>双尾检验</Select.Option>
105+
<Select.Option value={false}>单尾检验</Select.Option>
106+
</Select>
107+
</Form.Item>
108+
<Form.Item
109+
noStyle
110+
name='alpha'
111+
rules={[{ required: true, message: '请输入显著性水平' }]}
112+
>
113+
<InputNumber
114+
className='w-full'
115+
placeholder='请输入显著性水平'
116+
min={0}
117+
max={1}
118+
step={0.01}
119+
/>
120+
</Form.Item>
121+
</Space.Compact>
109122
</Form.Item>
110123
<Form.Item>
111124
<Button
@@ -125,29 +138,29 @@ export function OneSampleTTest() {
125138
{result ? (
126139
<div className='w-full h-full overflow-auto'>
127140

128-
<p className='text-lg mb-2 text-center w-full'>单样本T检验 ({result.alternative === 'two-sided' ? '双尾' : '单尾'})</p>
129-
<p className='text-xs mb-3 text-center w-full'>方法: Student's T Test | H<sub>0</sub>: 均值={result.expect} | 显著性水平(α): 0.05</p>
141+
<p className='text-lg mb-2 text-center w-full'>单样本T检验 ({result.twoside ? '双尾' : '单尾'})</p>
142+
<p className='text-xs mb-3 text-center w-full'>方法: Student's T Test | H<sub>0</sub>: 均值={result.expect} | 显著性水平(α): {result.alpha}</p>
130143
<table className='three-line-table'>
131144
<thead>
132145
<tr>
133146
<td>样本均值</td>
134147
<td>自由度</td>
135148
<td>t</td>
136149
<td>p</td>
137-
<td>95%置信区间</td>
150+
<td>{(100 - result.alpha * 100).toFixed(3)}%置信区间</td>
138151
<td>效应量 (Cohen's d)</td>
139152
<td>测定系数 (R<sup>2</sup>)</td>
140153
</tr>
141154
</thead>
142155
<tbody>
143156
<tr>
144-
<td>{(result.mean as number).toFixed(3)}</td>
145-
<td>{(result.df as number).toFixed(3)}</td>
146-
<td>{generatePResult(result.statistic, result.pValue).statistic}</td>
147-
<td>{generatePResult(result.statistic, result.pValue).p}</td>
148-
<td>{`[${(result.ci as [number, number])[0].toFixed(3)}, ${(result.ci as [number, number])[1].toFixed(3)})`}</td>
149-
<td>{(((result.mean as number) - result.expect) / (result.std as number)).toFixed(3)}</td>
150-
<td>{(((result.statistic as number) ** 2) / (((result.statistic as number) ** 2) + (result.df as number))).toFixed(3)}</td>
157+
<td>{result.m.mean.toFixed(3)}</td>
158+
<td>{result.m.df.toFixed(3)}</td>
159+
<td>{generatePResult(result.m.t, result.m.p).statistic}</td>
160+
<td>{generatePResult(result.m.t, result.m.p).p}</td>
161+
<td>{`[${result.m.ci[0].toFixed(3)}, ${result.m.ci[1].toFixed(3)})`}</td>
162+
<td>{result.m.cohenD.toFixed(3)}</td>
163+
<td>{result.m.r2.toFixed(3)}</td>
151164
</tr>
152165
</tbody>
153166
</table>
@@ -164,10 +177,10 @@ export function OneSampleTTest() {
164177
</thead>
165178
<tbody>
166179
<tr>
167-
<td>{(result.mean as number).toFixed(3)}</td>
168-
<td>{(result.std as number).toFixed(3)}</td>
169-
<td>{(result.df as number) + 1}</td>
170-
<td>{(result.df as number)}</td>
180+
<td>{result.m.mean.toFixed(3)}</td>
181+
<td>{result.m.std.toFixed(3)}</td>
182+
<td>{result.m.df + 1}</td>
183+
<td>{result.m.df}</td>
171184
</tr>
172185
</tbody>
173186
</table>

0 commit comments

Comments
 (0)