データ可視化において、様々な範囲の数値を統一された0-1の範囲に変換する「正規化」は重要な処理です。閾値を設定した正規化により、異常値の処理や表示範囲の制御が効率的に行えます。
閾値ベース正規化は、データの最小値・最大値ではなく、事前定義した閾値を基準に正規化を行う手法です。
// 基本的な正規化式
normalizedValue = (value - thresholdLower) / (thresholdUpper - thresholdLower)
// 通常の正規化(Min-Max Normalization)
const minMaxNormalize = (value: number, min: number, max: number) => {
return (value - min) / (max - min);
};
// 閾値ベース正規化(クランプ付き)
const thresholdNormalize = (
value: number,
thresholdLower: number,
thresholdUpper: number
) => {
return Math.max(0, Math.min(1,
(value - thresholdLower) / (thresholdUpper - thresholdLower)
));
};
import React, { useMemo } from 'react';
interface NormalizationProps {
cellBoxValue: number;
thresholdLower: number;
thresholdUpper: number;
}
const DataVisualization: React.FC<NormalizationProps> = ({
cellBoxValue,
thresholdLower,
thresholdUpper
}) => {
const normalizedValue = useMemo(() => {
return Math.max(
0,
Math.min(
1,
(cellBoxValue - thresholdLower) / (thresholdUpper - thresholdLower)
)
);
}, [cellBoxValue, thresholdLower, thresholdUpper]);
return (
<div>
<p>元の値: {cellBoxValue}</p>
<p>正規化値: {normalizedValue.toFixed(3)}</p>
{/* 正規化値に基づく可視化要素 */}
</div>
);
};
import { useMemo } from 'react';
interface UseThresholdNormalizationProps {
value: number;
thresholdLower: number;
thresholdUpper: number;
precision?: number;
}
const useThresholdNormalization = ({
value,
thresholdLower,
thresholdUpper,
precision = 3
}: UseThresholdNormalizationProps) => {
const normalizedValue = useMemo(() => {
const normalized = (value - thresholdLower) / (thresholdUpper - thresholdLower);
return Math.max(0, Math.min(1, normalized));
}, [value, thresholdLower, thresholdUpper]);
const formattedValue = useMemo(() => {
return Number(normalizedValue.toFixed(precision));
}, [normalizedValue, precision]);
return {
normalizedValue: formattedValue,
isMinThreshold: value <= thresholdLower,
isMaxThreshold: value >= thresholdUpper,
originalValue: value
};
};
const DataVisualizationChart: React.FC = () => {
const sensorData = [15, 25, 45, 65, 85, 95];
const thresholds = { lower: 20, upper: 80 };
const chartData = useMemo(() => {
return sensorData.map((value, index) => {
const normalized = Math.max(0, Math.min(1,
(value - thresholds.lower) / (thresholds.upper - thresholds.lower)
));
return {
x: index,
y: value,
normalizedY: normalized,
// 正規化値を使用して色を決定
color: value < thresholds.lower ? '#3b82f6' :
value > thresholds.upper ? '#ef4444' : '#10b981'
};
});
}, [sensorData]);
return (
<svg width="400" height="200">
{chartData.map((point, index) => (
<circle
key={index}
cx={point.x * 60 + 50}
cy={200 - (point.normalizedY * 150 + 25)}
r="6"
fill={point.color}
/>
))}
</svg>
);
};
import { motion } from 'framer-motion';
const AnimatedComponent: React.FC<{ value: number }> = ({ value }) => {
const { normalizedValue } = useThresholdNormalization({
value,
thresholdLower: 0,
thresholdUpper: 100
});
return (
<motion.div
animate={{
scale: 0.5 + normalizedValue * 0.5, // 0.5-1.0の範囲
opacity: 0.3 + normalizedValue * 0.7, // 0.3-1.0の範囲
}}
transition={{ duration: 0.3 }}
style={{
width: 100,
height: 100,
backgroundColor: `hsl(${120 * normalizedValue}, 70%, 50%)`,
borderRadius: '50%'
}}
>
{normalizedValue.toFixed(3)}
</motion.div>
);
};
interface DataSet {
name: string;
values: number[];
thresholds: { lower: number; upper: number };
}
const MultiDataNormalization: React.FC<{ datasets: DataSet[] }> = ({
datasets
}) => {
const normalizedDatasets = useMemo(() => {
return datasets.map(dataset => ({
...dataset,
normalizedValues: dataset.values.map(value =>
Math.max(0, Math.min(1,
(value - dataset.thresholds.lower) /
(dataset.thresholds.upper - dataset.thresholds.lower)
))
)
}));
}, [datasets]);
return (
<div className="grid gap-4">
{normalizedDatasets.map((dataset, index) => (
<div key={index} className="border p-4 rounded">
<h3 className="font-bold">{dataset.name}</h3>
<p className="text-sm text-gray-600">
閾値: {dataset.thresholds.lower} - {dataset.thresholds.upper}
</p>
<div className="flex gap-2 mt-2">
{dataset.normalizedValues.map((normalized, i) => (
<div
key={i}
className="w-8 h-8 rounded flex items-center justify-center text-xs"
style={{
backgroundColor: `rgba(59, 130, 246, ${normalized})`,
color: normalized > 0.5 ? 'white' : 'black'
}}
>
{normalized.toFixed(2)}
</div>
))}
</div>
</div>
))}
</div>
);
};
const DynamicThresholdDemo: React.FC = () => {
const [data] = useState([10, 25, 45, 60, 80, 95]);
const [thresholdLower, setThresholdLower] = useState(20);
const [thresholdUpper, setThresholdUpper] = useState(80);
const normalizedData = useMemo(() => {
return data.map(value => ({
original: value,
normalized: Math.max(0, Math.min(1,
(value - thresholdLower) / (thresholdUpper - thresholdLower)
))
}));
}, [data, thresholdLower, thresholdUpper]);
return (
<div className="p-6">
<div className="mb-4 space-y-2">
<label className="block">
下限閾値: {thresholdLower}
<input
type="range"
min="0"
max="50"
value={thresholdLower}
onChange={(e) => setThresholdLower(Number(e.target.value))}
className="block w-full mt-1"
/>
</label>
<label className="block">
上限閾値: {thresholdUpper}
<input
type="range"
min="50"
max="100"
value={thresholdUpper}
onChange={(e) => setThresholdUpper(Number(e.target.value))}
className="block w-full mt-1"
/>
</label>
</div>
<div className="grid grid-cols-6 gap-2">
{normalizedData.map((item, index) => (
<div key={index} className="text-center">
<div
className="w-12 h-12 rounded mx-auto mb-1"
style={{
backgroundColor: `hsl(${item.normalized * 120}, 70%, 50%)`,
}}
/>
<div className="text-xs">
<div>{item.original}</div>
<div className="text-gray-500">{item.normalized.toFixed(2)}</div>
</div>
</div>
))}
</div>
</div>
);
};
const batchNormalize = (
values: number[],
lower: number,
upper: number
): number[] => {
const range = upper - lower;
if (range === 0) {
return new Array(values.length).fill(0);
}
return values.map(value =>
Math.max(0, Math.min(1, (value - lower) / range))
);
};
// 使用例
const LargeDataVisualization: React.FC = () => {
const [largeDataset] = useState(() =>
Array.from({ length: 1000 }, () => Math.random() * 200)
);
const normalizedData = useMemo(() =>
batchNormalize(largeDataset, 20, 180),
[largeDataset]
);
return (
<div className="grid grid-cols-20 gap-1">
{normalizedData.slice(0, 100).map((value, index) => (
<div
key={index}
className="w-4 h-4"
style={{
backgroundColor: `rgba(59, 130, 246, ${value})`,
}}
/>
))}
</div>
);
};
閾値ベース正規化は、データ可視化において柔軟で強力な手法です。
主要なメリット:
実装のポイント:
適切に実装された閾値正規化により、より直感的で使いやすいデータ可視化インターフェースを構築できます。
この記事は React 18.x、TypeScript 5.x、Node.js 18.x以上を基準としています。