阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 数据转换与预处理

数据转换与预处理

作者:陈川 阅读数:17972人阅读 分类: ECharts

数据转换与预处理

数据可视化过程中,原始数据往往不能直接用于图表渲染。ECharts作为流行的可视化库,提供了丰富的数据处理能力。从数据清洗到格式转换,再到聚合计算,每个环节都影响着最终呈现效果。

数据格式标准化

ECharts支持多种数据格式,但推荐使用键值对数组形式。原始CSV或JSON数据经常需要转换:

// 原始数据
const rawData = [
  { year: '2020', sales: 1250 },
  { year: '2021', sales: 1870 },
  { year: '2022', sales: 2100 }
];

// 转换为ECharts需要的格式
const chartData = {
  xAxis: rawData.map(item => item.year),
  series: [{
    data: rawData.map(item => item.sales)
  }]
};

时间数据需要特别注意格式统一。使用moment.js或原生Date对象处理时间格式:

const timeData = [
  { date: '2023-01', value: 42 },
  { date: '2023-02', value: 78 }
];

// 转换为时间戳格式
const processedData = timeData.map(item => ({
  ...item,
  date: new Date(item.date + '-01').getTime()
}));

数据清洗与过滤

异常值处理是数据预处理的关键环节。通过设定阈值过滤不合理数据:

const dirtyData = [12, 45, 999, 32, -5, 28];

// 过滤超出0-100范围的值
const cleanData = dirtyData.filter(
  value => value >= 0 && value <= 100
);

缺失值处理可采用插值法。线性插值示例:

const incompleteData = [
  { x: 1, y: 10 },
  { x: 2, y: null },
  { x: 3, y: 30 }
];

// 线性插值补全缺失值
for (let i = 1; i < incompleteData.length - 1; i++) {
  if (incompleteData[i].y === null) {
    incompleteData[i].y = 
      (incompleteData[i-1].y + incompleteData[i+1].y) / 2;
  }
}

数据聚合与分组

大数据量时需要聚合处理。按时间维度聚合的典型示例:

const dailyData = [
  { date: '2023-01-01', category: 'A', value: 10 },
  { date: '2023-01-01', category: 'B', value: 20 },
  // ...更多数据
];

// 按月聚合
const monthlyData = dailyData.reduce((acc, curr) => {
  const month = curr.date.substring(0, 7);
  if (!acc[month]) acc[month] = 0;
  acc[month] += curr.value;
  return acc;
}, {});

// 转换为ECharts格式
const seriesData = Object.entries(monthlyData).map(
  ([month, value]) => [month, value]
);

分类数据分组统计:

const products = [
  { category: '电子', price: 999 },
  { category: '服装', price: 199 },
  // ...更多商品
];

// 按类别分组计算平均价格
const categoryStats = products.reduce((acc, product) => {
  if (!acc[product.category]) {
    acc[product.category] = { sum: 0, count: 0 };
  }
  acc[product.category].sum += product.price;
  acc[product.category].count++;
  return acc;
}, {});

// 计算平均值
const result = Object.entries(categoryStats).map(
  ([category, stats]) => ({
    category,
    avgPrice: stats.sum / stats.count
  })
);

数据映射与转换

将原始值映射到可视化属性是常见需求。颜色映射示例:

const temperatureData = [12, 18, 25, 30, 15];

// 温度到颜色的映射函数
function tempToColor(temp) {
  if (temp < 15) return '#3498db';  // 冷色
  if (temp < 25) return '#2ecc71';  // 舒适
  return '#e74c3c';                // 炎热
}

const coloredData = temperatureData.map(temp => ({
  value: temp,
  itemStyle: { color: tempToColor(temp) }
}));

数值范围归一化处理:

const rawValues = [50, 120, 80, 200];

// 归一化到0-1范围
const max = Math.max(...rawValues);
const normalized = rawValues.map(v => v / max);

// 再映射到特定范围(如50-200像素)
const range = [50, 200];
const finalValues = normalized.map(
  v => range[0] + v * (range[1] - range[0])
);

时间序列处理

处理时间数据时需要特殊转换。周数据转换为日历坐标:

const weekData = [
  { day: '周一', value: 12 },
  { day: '周二', value: 19 },
  // ...完整周数据
];

// 转换为日历坐标系需要的格式
const calendarData = weekData.map((item, index) => [
  index,        // x轴坐标
  item.value,   // y轴值
  item.day      // 显示标签
]);

处理不连续时间序列:

const sparseTimeData = [
  { time: '2023-01', value: 10 },
  { time: '2023-03', value: 20 },
  { time: '2023-06', value: 15 }
];

// 补全缺失月份数据
const allMonths = ['01','02','03','04','05','06'].map(m => `2023-${m}`);
const completeData = allMonths.map(month => {
  const existing = sparseTimeData.find(d => d.time === month);
  return existing || { time: month, value: 0 };
});

多维数据透视

处理多维数据时需要降维展示。使用dataset和dimensions配置:

const multiDimData = [
  { product: '手机', region: '华东', sales: 1200 },
  { product: '电脑', region: '华北', sales: 800 },
  // ...更多数据
];

option = {
  dataset: {
    source: multiDimData,
    dimensions: ['product', 'region', 'sales']
  },
  series: {
    type: 'bar',
    encode: {
      x: 'product',
      y: 'sales',
      itemName: 'region'
    }
  }
};

性能优化处理

大数据量时需要进行采样优化。等距采样算法:

const largeData = [...Array(10000)].map((_, i) => ({
  x: i,
  y: Math.sin(i / 100)
}));

// 等距采样保留100个点
const sampleSize = 100;
const step = Math.floor(largeData.length / sampleSize);
const sampledData = [];
for (let i = 0; i < largeData.length; i += step) {
  sampledData.push(largeData[i]);
}

增量数据更新策略:

let allData = [...initialData];

// 新数据到达时
function handleNewData(newPoints) {
  // 保留最近1000个点
  if (allData.length + newPoints.length > 1000) {
    allData = allData.slice(newPoints.length);
  }
  allData.push(...newPoints);
  
  // 更新图表
  myChart.setOption({
    series: [{ data: allData }]
  });
}

交互数据处理

动态筛选数据示例:

const fullData = [
  { name: '北京', value: 123 },
  { name: '上海', value: 156 },
  // ...更多城市数据
];

function filterData(minValue) {
  return fullData.filter(item => item.value >= minValue);
}

// 滑块交互
document.getElementById('rangeSlider').addEventListener('input', (e) => {
  const filtered = filterData(parseInt(e.target.value));
  myChart.setOption({
    series: [{ data: filtered }]
  });
});

地理数据转换

处理GeoJSON数据时需要特殊转换:

// 从GeoJSON提取坐标边界
function getBounds(features) {
  return features.reduce((bounds, feature) => {
    const [minX, minY, maxX, maxY] = turf.bbox(feature);
    bounds.minX = Math.min(bounds.minX || Infinity, minX);
    bounds.minY = Math.min(bounds.minY || Infinity, minY);
    bounds.maxX = Math.max(bounds.maxX || -Infinity, maxX);
    bounds.maxY = Math.max(bounds.maxY || -Infinity, maxY);
    return bounds;
  }, {});
}

坐标转换示例:

// WGS84转Web墨卡托
function wgs84ToMercator(lng, lat) {
  const x = lng * 20037508.34 / 180;
  const y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
  return [x, y * 20037508.34 / 180];
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌