Skip to content

Commit

Permalink
feat: 重测信度/复本信度/分半信度/同质性信度
Browse files Browse the repository at this point in the history
  • Loading branch information
LeafYeeXYZ committed Oct 22, 2024
1 parent 55441ca commit 1dd709e
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
- [5.4.2 Levene 检验](#542-levene-检验)
- [5.5 相关和回归](#55-相关和回归)
- [5.5.1 Pearson 相关检验](#551-pearson-相关检验)
- [5.6 信度分析](#56-信度分析)
- [5.6.1 重测信度/复本信度](#561-重测信度复本信度)
- [5.6.2 分半信度](#562-分半信度)
- [5.6.3 同质性信度](#563-同质性信度)
- [6 工具视图](#6-工具视图)
- [6.1 正态分布可视化演示工具](#61-正态分布可视化演示工具)
- [6.2 统计量与P值转换工具](#62-统计量与p值转换工具)
Expand Down Expand Up @@ -228,6 +232,36 @@ Pearson 相关检验用于检验两组数据之间的线性相关性. 在 Pearso

![](readme/stat-10.png)

### 5.6 信度分析

信度指测量结果的稳定性, 即多次测量能得到一致的结果. 选择的信度测量方法取决于可能的误差来源, 详见[我的心理测量学笔记](https://blog.leafyee.xyz/2023/11/02/心理测量学/#x2B50-信度)

#### 5.6.1 重测信度/复本信度

重测信度指同一量表, 同一受测群体, 不同时间, 两次施测, 求积差相关; 复本信度指以两个测验复本来测量同一群体, 然后求受测者群体在这两个测验上得分的积差相关. 在本页面中, 你可以选择你要进行检验的变量 (两个), 点击 `计算` 按钮即可进行重测信度/复本信度检验

可以接受的信度临界值取决于研究的具体情况, 如下图所示

| 信度临界值 | 使用演示 |
| :---: | :---: |
| ![](readme/stat-11.png) | ![](readme/stat-12.png) |

#### 5.6.2 分半信度

分半信度指将一个量表分成两半, 每半都包含相同数量的项目, 然后计算两半的得分, 求两半得分的相关系数. 得到的相关系数还需要通过公式修正, 以得到最终的分半信度. 在本页面中, 你可以分别选择两半的变量列表, 点击 `计算` 按钮即可进行分半信度检验

| 信度临界值 | 使用演示 |
| :---: | :---: |
| ![](readme/stat-11.png) | ![](readme/stat-13.png) |

#### 5.6.3 同质性信度

如果各个题目是测量同一心理特质, 则各个题目得分间相关越高则信度越好; 同质性信度即是各个题目与总分的相关系数, 也称 alpha 系数. 在本页面中, 你可以选择量表的所有题目, 点击 `计算` 按钮即可进行同质性信度检验

**应用中, alpha 的值至少要大于0.5, 最好能大于0.7**

![](readme/stat-14.png)

## 6 工具视图

工具视图中的功能无需导入数据即可食用. 你可在页面上方选择你要使用的工具, 进入对应的工具页面
Expand Down
Binary file added readme/stat-11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/stat-12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/stat-13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/stat-14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions src/components/StatisticsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { KolmogorovSmirnovTest } from '../statistics/KolmogorovSmirnovTest'
import { PearsonCorrelationTest } from '../statistics/PearsonCorrelationTest'
import { LeveneTest } from '../statistics/LeveneTest'
import { Description } from '../statistics/Description'
import { CorrReliability } from '../statistics/CorrReliability'
import { HalfReliability } from '../statistics/HalfReliability'
import { HomoReliability } from '../statistics/HomoReliability'
import { Cascader } from 'antd'
import { useState } from 'react'

Expand Down Expand Up @@ -66,6 +69,24 @@ const CASCADER_OPTIONS: Option[] = [
},
],
},
{
value: 'Reliability',
label: '信度分析',
children: [
{
value: 'CorrReliability',
label: '重测或复本信度',
},
{
value: 'HalfReliability',
label: '分半信度',
},
{
value: 'HomoReliability',
label: '同质性信度',
},
],
},
]
const CASCADER_ONCHANGE = (value: string[], set: (page: React.ReactElement) => void) => {
switch (value[1]) {
Expand All @@ -87,6 +108,15 @@ const CASCADER_ONCHANGE = (value: string[], set: (page: React.ReactElement) => v
case 'PearsonCorrelationTest':
set(<PearsonCorrelationTest />)
break
case 'CorrReliability':
set(<CorrReliability />)
break
case 'HalfReliability':
set(<HalfReliability />)
break
case 'HomoReliability':
set(<HomoReliability />)
break
default:
set(<Description />)
}
Expand Down
122 changes: 122 additions & 0 deletions src/statistics/CorrReliability.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useZustand } from '../lib/useZustand'
import { Select, Button, Form } from 'antd'
import { useState } from 'react'
import { flushSync } from 'react-dom'
import { corr } from 'mathjs'

type Option = {
/** 变量名 */
variables: [string, string]
}
type Result = {
r: number
} & Option

export function CorrReliability() {

const { dataCols, dataRows, messageApi } = useZustand()
const [result, setResult] = useState<Result | null>(null)
const [disabled, setDisabled] = useState<boolean>(false)
const handleCalculate = (values: Option) => {
const timestamp = Date.now()
try {
messageApi?.loading('正在处理数据...')
const filteredRows = dataRows.filter((row) => values.variables.every((variable) => typeof row[variable] !== 'undefined' && !isNaN(Number(row[variable]))))
const data = values.variables.map((variable) => filteredRows.map((row) => Number(row[variable])))
const r = corr(data[0], data[1])
setResult({
...values,
r: Number(r),
})
messageApi?.destroy()
messageApi?.success(`数据处理完成, 用时 ${Date.now() - timestamp} 毫秒`)
} catch (error) {
messageApi?.destroy()
messageApi?.error(`数据处理失败: ${error instanceof Error ? error.message : JSON.stringify(error)}`)
}
}

return (
<div className='w-full h-full overflow-hidden flex justify-start items-center gap-4 p-4'>

<div className='w-96 h-full flex flex-col justify-center items-center rounded-md border bg-gray-50 px-4 overflow-auto'>

<Form<Option>
className='w-full py-4'
layout='vertical'
onFinish={(values) => {
flushSync(() => setDisabled(true))
handleCalculate(values)
flushSync(() => setDisabled(false))
}}
autoComplete='off'
disabled={disabled}
>
<Form.Item
label='选择变量(两个)'
name='variables'
rules={[
{ required: true, message: '请选择变量' },
{ type: 'array', min: 2, max: 2, message: '请选择两个变量' },
]}
>
<Select
className='w-full'
placeholder='请选择变量'
mode='multiple'
>
{dataCols.map((col) => col.type === '等距或等比数据' && (
<Select.Option key={col.name} value={col.name}>
{col.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item>
<Button
className='w-full mt-4'
type='default'
htmlType='submit'
>
计算
</Button>
</Form.Item>
</Form>

</div>

<div className='w-[calc(100%-24rem)] h-full flex flex-col justify-start items-center gap-4 rounded-md border bg-white overflow-auto p-8'>

{result ? (
<div className='w-full h-full overflow-auto'>

<p className='text-lg mb-2 text-center w-full'>重测信度/复本信度分析</p>
<table className='three-line-table'>
<thead>
<tr>
<td>配对变量A</td>
<td>配对变量B</td>
<td>相关系数(r<sub>xx</sub>)</td>
</tr>
</thead>
<tbody>
<tr>
<td>{result.variables[0]}</td>
<td>{result.variables[1]}</td>
<td>{result.r.toFixed(3)}</td>
</tr>
</tbody>
</table>

</div>
) : (
<div className='w-full h-full flex justify-center items-center'>
<span>请填写参数并点击计算</span>
</div>
)}

</div>

</div>
)
}
163 changes: 163 additions & 0 deletions src/statistics/HalfReliability.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { useZustand } from '../lib/useZustand'
import { Select, Button, Form } from 'antd'
import { useState } from 'react'
import { corr } from 'mathjs'
import { flushSync } from 'react-dom'

type Option = {
/** 前一半变量名 */
variablesA: string[]
/** 后一半变量名 */
variablesB: string[]
}
type Result = {
/** 矫正后的相关系数 */
correctedR: number
} & Option

export function HalfReliability() {

const { dataCols, dataRows, messageApi } = useZustand()
const [result, setResult] = useState<Result | null>(null)
const [disabled, setDisabled] = useState<boolean>(false)
const handleCalculate = (values: Option) => {
const timestamp = Date.now()
try {
messageApi?.loading('正在处理数据...')
const filteredRows = dataRows.filter((row) => values.variablesA.concat(values.variablesB).every((variable) => typeof row[variable] !== 'undefined' && !isNaN(Number(row[variable]))))
const meansA = filteredRows.map((row) => values.variablesA.reduce((acc, variable) => acc + Number(row[variable]), 0) / values.variablesA.length)
const meansB = filteredRows.map((row) => values.variablesB.reduce((acc, variable) => acc + Number(row[variable]), 0) / values.variablesB.length)
const r = Number(corr(meansA, meansB))
const correctedR = 2 * r / (1 + r)
setResult({
...values,
correctedR,
})
messageApi?.destroy()
messageApi?.success(`数据处理完成, 用时 ${Date.now() - timestamp} 毫秒`)
} catch (error) {
messageApi?.destroy()
messageApi?.error(`数据处理失败: ${error instanceof Error ? error.message : JSON.stringify(error)}`)
}
}

return (
<div className='w-full h-full overflow-hidden flex justify-start items-center gap-4 p-4'>

<div className='w-96 h-full flex flex-col justify-center items-center rounded-md border bg-gray-50 px-4 overflow-auto'>

<Form<Option>
className='w-full py-4'
layout='vertical'
onFinish={(values) => {
flushSync(() => setDisabled(true))
handleCalculate(values)
flushSync(() => setDisabled(false))
}}
autoComplete='off'
disabled={disabled}
>
<Form.Item
label='选择前一半变量'
name='variablesA'
rules={[
{ required: true, message: '请选择变量' },
({ getFieldValue }) => ({
validator(_, value) {
if (value?.some((variable: string) => getFieldValue('variablesB')?.includes(variable))) {
return Promise.reject('前后两半变量不能重复')
}
return Promise.resolve()
}
})
]}
>
<Select
className='w-full'
placeholder='请选择变量'
mode='multiple'
>
{dataCols.map((col) => col.type === '等距或等比数据' && (
<Select.Option key={col.name} value={col.name}>
{col.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label='选择后一半变量'
name='variablesB'
rules={[
{ required: true, message: '请选择变量' },
({ getFieldValue }) => ({
validator(_, value) {
if (value?.some((variable: string) => getFieldValue('variablesA')?.includes(variable))) {
return Promise.reject('前后两半变量不能重复')
}
return Promise.resolve()
}
})
]}
>
<Select
className='w-full'
placeholder='请选择变量'
mode='multiple'
>
{dataCols.map((col) => col.type === '等距或等比数据' && (
<Select.Option key={col.name} value={col.name}>
{col.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item>
<Button
className='w-full mt-4'
type='default'
htmlType='submit'
>
计算
</Button>
</Form.Item>
</Form>

</div>

<div className='w-[calc(100%-24rem)] h-full flex flex-col justify-start items-center gap-4 rounded-md border bg-white overflow-auto p-8'>

{result ? (
<div className='w-full h-full overflow-auto'>

<p className='text-lg mb-2 text-center w-full'>分半信度分析</p>
<table className='three-line-table'>
<thead>
<tr>
<td>前半部分题目数</td>
<td>后半部分题目数</td>
<td>修正后相关系数(r<sub>xx</sub>)</td>
</tr>
</thead>
<tbody>
<tr>
<td>{result.variablesA.length}</td>
<td>{result.variablesB.length}</td>
<td>{result.correctedR.toFixed(3)}</td>
</tr>
</tbody>
</table>
<p className='text-xs mt-3 text-center w-full'>前半部分题目: {result.variablesA.join(', ')}</p>
<p className='text-xs mt-2 text-center w-full'>后半部分题目: {result.variablesB.join(', ')}</p>

</div>
) : (
<div className='w-full h-full flex justify-center items-center'>
<span>请填写参数并点击计算</span>
</div>
)}

</div>

</div>
)
}
Loading

0 comments on commit 1dd709e

Please sign in to comment.