数据分析方法--趋势分析法(二)

参考趋势模型生成的java方法:


import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.math3.linear.*;

public class TrendAnalysisUtils {

    public static void main(String[] args) {
        List<String> xAxisDatas = new ArrayList<>();   // 时间序列
        // 示例数据
        xAxisDatas.add("2020-03-01");
        xAxisDatas.add("2021-03-02");
        xAxisDatas.add("2022-03-03");
        xAxisDatas.add("2023-03-04");

        // xAxisDatas.add("2023-01");
        // xAxisDatas.add("2023-02");
        // xAxisDatas.add("2023-03");
        // xAxisDatas.add("2023-04");

        // xAxisDatas.add("2020");
        // xAxisDatas.add("2021");
        // xAxisDatas.add("2022");
        // xAxisDatas.add("2023");

        List<BigDecimal> seriesDatas = new ArrayList<>();  // 具体数据
        seriesDatas.add(new BigDecimal("26"));
        seriesDatas.add(new BigDecimal("51"));
        seriesDatas.add(new BigDecimal("43"));
        seriesDatas.add(new BigDecimal("43"));

        List<Double> numericDates = parseDates(xAxisDatas);
        List<Double> seriesDataDoubles = new ArrayList<>();
        for (BigDecimal value : seriesDatas) {
            seriesDataDoubles.add(value.doubleValue());
        }

        System.out.println(analyzeLinearTrend(numericDates, seriesDataDoubles));
        System.out.println(analyzeLogarithmicTrend(numericDates, seriesDataDoubles));
        System.out.println(analyzeExponentialTrend(numericDates, seriesDataDoubles));
        System.out.println(analyzePowerTrend(numericDates, seriesDataDoubles));
        System.out.println(analyzePolynomialTrend(numericDates, seriesDataDoubles));
    }


    public static String trendAnalysis(List<String> xAxisDatas, List<BigDecimal> seriesDatas) {
        StringBuilder buf = new StringBuilder();
        List<Double> numericDates = parseDates(xAxisDatas);
        List<Double> seriesDataDoubles = new ArrayList<>();
        for (BigDecimal value : seriesDatas) {
            seriesDataDoubles.add(value.doubleValue());
        }

        buf.append("<br>基于线性趋势模型分析:").append(analyzeLinearTrend(numericDates, seriesDataDoubles));
        buf.append("<br>基于对数趋势模型分析:").append(analyzeLogarithmicTrend(numericDates, seriesDataDoubles));
        buf.append("<br>基于指数趋势模型分析:").append(analyzeExponentialTrend(numericDates, seriesDataDoubles));
        buf.append("<br>基于幂函数趋势分析:").append(analyzePowerTrend(numericDates, seriesDataDoubles));
        buf.append("<br>基于多项式趋势线模型分析:").append(analyzePolynomialTrend(numericDates, seriesDataDoubles));

        return buf.toString();
    }

    /**
     * 解析日期字符串并转换为数字表示形式。
     *
     * @param xAxisDatas 日期字符串列表
     * @return 数字表示的日期列表
     */
    private static List<Double> parseDates(List<String> xAxisDatas) {
        List<Double> numericDates = new ArrayList<>();
        DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("yyyy");
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
        DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        for (String dateString : xAxisDatas) {
            try {
                LocalDate date;
                if (dateString.matches("\\d{4}-\\d{2}-\\d{2}")) { // yyyy-MM-dd
                    date = LocalDate.parse(dateString, dayFormatter);
                } else if (dateString.matches("\\d{4}-\\d{2}")) { // yyyy-MM
                    date = LocalDate.parse(dateString + "-01", dayFormatter);
                } else { // yyyy
                    date = LocalDate.parse(dateString + "-01-01", dayFormatter);
                }
                numericDates.add(dateToNumeric(date));
            } catch (Exception e) {
                System.err.println("Error parsing date string '" + dateString + "': " + e.getMessage() + ", unparsed text found at index " + dateString.indexOf('-'));
            }
        }

        return numericDates;
    }
    /**
     * 将日期转换为相对于某个基准日期的天数
     * @param date 需要转换的日期
     * @return 从基准日期开始的天数
     */
    private static double dateToNumeric(LocalDate date) {
        LocalDate epoch = LocalDate.ofEpochDay(0);
        long daysSinceEpoch = date.toEpochDay() - epoch.toEpochDay();
        return daysSinceEpoch;
    }


    /**
     * 分析数据是否符合线性趋势
     *
     * 线性趋势模型:y = a + bx,其中 a 和 b 是模型的系数。
     * 如果 b > 0,那么随着 x 的增加,y 也会增加,且这种增加是均匀的;
     * 如果 b < 0,那么随着 x 的增加,y 会减少,且这种减少也是均匀的。
     *
     * @param numericDates 数字表示的时间序列
     * @param seriesData 数据点列表
     * @return 趋势分析结果字符串
     */
    public static String analyzeLinearTrend(List<Double> numericDates, List<Double> seriesData) {
        // 计算 x 的总和
        double sumX = numericDates.stream().mapToDouble(Double::doubleValue).sum();
        // 计算 y 的总和
        double sumY = seriesData.stream().mapToDouble(Double::doubleValue).sum();
        // 计算 x * y 的总和
        double sumXY = 0;
        // 计算 x^2 的总和
        double sumXSquare = 0;

        // 遍历所有数据点并计算所需的和
        for (int i = 0; i < numericDates.size(); i++) {
            sumXY += numericDates.get(i) * seriesData.get(i);
            sumXSquare += numericDates.get(i) * numericDates.get(i);
        }

        // 获取数据点的数量
        int n = numericDates.size();
        // 计算系数 b (斜率)
        double b = (n * sumXY - sumX * sumY) / (n * sumXSquare - Math.pow(sumX, 2));
        // 计算系数 a (截距)
        double a = (sumY - b * sumX) / n;

        // 根据系数 b 的符号返回分析结果
        return b > 0 ? "[指标]平稳上升,增长趋势稳定" : "[指标]持续下降";
    }

    /**
     * 分析数据是否符合对数趋势
     *
     * 对数趋势模型:y = a + b * ln(x),其中 a 和 b 是模型的系数。
     * 如果 b > 0,那么随着 x 的增加,y 也会增加,但增加的速度逐渐减慢;
     * 如果 b < 0,那么随着 x 的增加,y 会减少,减少的速度也逐渐减慢。
     *
     * @param numericDates 数字表示的时间序列
     * @param seriesData 数据点列表
     * @return 趋势分析结果字符串
     */
    public static String analyzeLogarithmicTrend(List<Double> numericDates, List<Double> seriesData) {
        // 计算 ln(x) 的总和
        double sumX = numericDates.stream().mapToDouble(Math::log).sum();
        // 计算 y 的总和
        double sumY = seriesData.stream().mapToDouble(Double::doubleValue).sum();
        // 计算 ln(x) * y 的总和
        double sumXY = 0;
        // 计算 (ln(x))^2 的总和
        double sumXSquare = 0;

        // 遍历所有数据点并计算所需的和
        for (int i = 0; i < numericDates.size(); i++) {
            sumXY += Math.log(numericDates.get(i)) * seriesData.get(i);
            sumXSquare += Math.log(numericDates.get(i)) * Math.log(numericDates.get(i));
        }

        // 获取数据点的数量
        int n = numericDates.size();
        // 计算系数 b (斜率)
        double b = (n * sumXY - sumX * sumY) / (n * sumXSquare - Math.pow(sumX, 2));
        // 计算系数 a (截距)
        double a = (sumY - b * sumX) / n;

        // 根据系数 b 的符号返回分析结果
        return b > 0 ? "[指标]总体呈现上升趋势,初期增长速度快,后期增速放缓趋于平稳" : "[指标]总体呈现下降趋势,初期下降速度快,后期降速放缓";
    }

    /**
     * 分析数据是否符合指数趋势
     *
     * 指数趋势模型:y = exp(a + bx),其中 a 和 b 是模型的系数。
     * 如果 b > 0,那么随着 x 的增加,y 也会以指数方式增加;
     * 如果 b < 0,那么随着 x 的增加,y 会以指数方式减少。
     *
     * @param numericDates 数字表示的时间序列
     * @param seriesData 数据点列表
     * @return 趋势分析结果字符串
     */
    public static String analyzeExponentialTrend(List<Double> numericDates, List<Double> seriesData) {
        // 计算 ln(y) 的列表
        List<Double> logSeriesData = new ArrayList<>();
        for (Double value : seriesData) {
            logSeriesData.add(Math.log(value));
        }

        // 计算 x 的总和
        double sumX = numericDates.stream().mapToDouble(Double::doubleValue).sum();
        // 计算 ln(y) 的总和
        double sumY = logSeriesData.stream().mapToDouble(Double::doubleValue).sum();
        // 计算 x * ln(y) 的总和
        double sumXY = 0;
        // 计算 x^2 的总和
        double sumXSquare = 0;

        // 遍历所有数据点并计算所需的和
        for (int i = 0; i < numericDates.size(); i++) {
            sumXY += numericDates.get(i) * logSeriesData.get(i);
            sumXSquare += numericDates.get(i) * numericDates.get(i);
        }

        // 获取数据点的数量
        int n = numericDates.size();
        // 计算系数 b (斜率)
        double b = (n * sumXY - sumX * sumY) / (n * sumXSquare - Math.pow(sumX, 2));
        // 计算系数 a (截距)
        double a = (sumY - b * sumX) / n;

        // 根据系数 b 的符号返回分析结果
        return b > 0 ? "[指标]总体呈现上升趋势,增长速度越来越快" : "[指标]总体呈现下降趋势,下降速度越来越快";
    }

    /**
     * 分析数据是否符合幂函数趋势
     *
     * 幂函数趋势模型:y = ax^b,其中 a 和 b 是模型的系数。
     * 如果 b > 0,那么随着 x 的增加,y 也会增加;
     * 如果 b < 0,那么随着 x 的增加,y 会减少。
     *
     * @param numericDates 数字表示的时间序列
     * @param seriesData 数据点列表
     * @return 趋势分析结果字符串
     */
    public static String analyzePowerTrend(List<Double> numericDates, List<Double> seriesData) {
        // 计算 ln(x) 的列表
        List<Double> logDates = new ArrayList<>();
        // 计算 ln(y) 的列表
        List<Double> logSeriesData = new ArrayList<>();
        for (Double value : numericDates) {
            logDates.add(Math.log(value));
        }
        for (Double value : seriesData) {
            logSeriesData.add(Math.log(value));
        }

        // 计算 ln(x) 的总和
        double sumX = logDates.stream().mapToDouble(Double::doubleValue).sum();
        // 计算 ln(y) 的总和
        double sumY = logSeriesData.stream().mapToDouble(Double::doubleValue).sum();
        // 计算 ln(x) * ln(y) 的总和
        double sumXY = 0;
        // 计算 (ln(x))^2 的总和
        double sumXSquare = 0;

        // 遍历所有数据点并计算所需的和
        for (int i = 0; i < logDates.size(); i++) {
            sumXY += logDates.get(i) * logSeriesData.get(i);
            sumXSquare += logDates.get(i) * logDates.get(i);
        }

        // 获取数据点的数量
        int n = logDates.size();
        // 计算系数 b (斜率)
        double b = (n * sumXY - sumX * sumY) / (n * sumXSquare - Math.pow(sumX, 2));
        // 计算系数 a (截距)
        double a = Math.exp((sumY - b * sumX) / n);

        // 根据系数 b 的符号返回分析结果
        return b > 0 ? "[指标]总体呈现上升趋势,增速很快" : "[指标]总体呈现下降趋势,下降速度很快";
    }


    /**
     * 分析多项式趋势线模型
     *
     * 多项式趋势模型:y = a_n * x^n + a_{n-1} * x^{n-1} + ... + a_1 * x + a_0,其中 a_i 是模型的系数。
     * 该方法拟合多项式回归模型并计算残差的标准差来评估波动性。
     *
     * @param numericDates 数字表示的时间序列
     * @param seriesData 数据点列表
     * @return 波动分析结果字符串
     */
    public static String analyzePolynomialTrend(List<Double> numericDates, List<Double> seriesData) {
        double[] coefficients = fitPolynomialRegression(numericDates, seriesData, 6); // 拟合多项式回归模型

        double standardDeviation = calculateStandardDeviation(coefficients, numericDates, seriesData); // 计算残差的标准差

        return standardDeviation > 10 ? "[指标]波动较大,不够稳定" : "[指标]波动较小,比较稳定"; // 根据标准差判断波动性
    }

    /**
     * 拟合多项式回归模型
     *
     * @param numericDates 数字表示的时间序列
     * @param seriesData 数据点列表
     * @param degree 多项式的阶数
     * @return 多项式系数数组
     */
    public static double[] fitPolynomialRegression(List<Double> numericDates, List<Double> seriesData, int degree) {
        int n = numericDates.size(); // 数据点数量
        RealMatrix designMatrix = new Array2DRowRealMatrix(n, degree + 1); // 设计矩阵
        RealVector responseVector = new ArrayRealVector(seriesData.stream().mapToDouble(Double::doubleValue).toArray()); // 响应向量

        // 构建设计矩阵
        for (int i = 0; i < n; i++) {
            double date = numericDates.get(i);
            if (date <= 0) {
                throw new IllegalArgumentException("Invalid date value encountered.");
            }
            for (int j = 0; j <= degree; j++) {
                designMatrix.setEntry(i, j, Math.pow(date, j));
            }
        }

        // 使用最小二乘法求解多项式系数
        RealMatrix XTX = designMatrix.transpose().multiply(designMatrix);
        RealMatrix XTY = new Array2DRowRealMatrix(designMatrix.getColumnDimension(), 1);

        // 计算 XTY
        for (int j = 0; j < designMatrix.getColumnDimension(); j++) {
            XTY.setEntry(j, 0, designMatrix.getColumnVector(j).dotProduct(responseVector));
        }

        DecompositionSolver solver = new LUDecomposition(XTX).getSolver();
        RealVector coefficients = solver.solve(XTY.getColumnVector(0));

        return coefficients.toArray();
    }

    /**
     * 创建设计矩阵(包含基函数的矩阵)
     * @param numericDates 数字表示的日期列表
     * @param degree 多项式的阶数
     * @return 设计矩阵
     */
    private static RealMatrix createDesignMatrix(List<Double> numericDates, int degree) {
        double[][] matrix = new double[numericDates.size()][degree + 1]; // 初始化矩阵

        for (int i = 0; i < numericDates.size(); i++) {
            for (int j = 0; j <= degree; j++) {
                matrix[i][j] = Math.pow(numericDates.get(i), j); // 计算基函数值
            }
        }

        return new Array2DRowRealMatrix(matrix); // 返回设计矩阵
    }

    /**
     * 创建响应变量矩阵
     * @param seriesData 数据点列表
     * @return 响应变量矩阵
     */
    private static RealMatrix createYMatrix(List<Double> seriesData) {
        double[] yValues = new double[seriesData.size()]; // 初始化数组

        for (int i = 0; i < seriesData.size(); i++) {
            yValues[i] = seriesData.get(i); // 存储数据点
        }

        return new Array2DRowRealMatrix(yValues); // 返回响应变量矩阵
    }

    /**
     * 计算残差的标准差
     *
     * @param coefficients 多项式系数
     * @param numericDates 数字表示的时间序列
     * @param seriesData 数据点列表
     * @return 残差的标准差
     */
    public static double calculateStandardDeviation(double[] coefficients, List<Double> numericDates, List<Double> seriesData) {
        int n = numericDates.size();
        double sumOfSquares = 0;

        for (int i = 0; i < n; i++) {
            double predictedValue = 0;
            double date = numericDates.get(i);

            // 计算预测值
            for (int j = 0; j < coefficients.length; j++) {
                predictedValue += coefficients[j] * Math.pow(date, j);
            }

            double actualValue = seriesData.get(i);
            double residual = actualValue - predictedValue;

            sumOfSquares += residual * residual;
        }

        double meanSquareError = sumOfSquares / n;
        return Math.sqrt(meanSquareError);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值