2024/9/8周报

摘要

智慧水务项目中,需要对采集的总氮、氨氮、化学需氧量、硝态氮、总磷、硝态氮等数据进行数据处理与挖掘,因此本周对数据挖掘相关内容进行初步学习,数据挖掘是指从大量的数据中通过算法和模型发现有用信息和知识的过程。

Abstract

In the Intelligent Water project, it is necessary to process and mine the collected data such as total nitrogen, ammonia nitrogen, chemical oxygen demand, nitrate nitrogen, total phosphorus, and nitrate nitrogen. Therefore, this week we will conduct preliminary learning on data mining related content. Data mining refers to the process of discovering useful information and knowledge from a large amount of data through algorithms and models.

数据挖掘

数据挖掘(Data Mining)是从大量数据中提取有用信息的过程,这些信息以人类可理解的模式呈现出来,可以用来做出决策或发现知识。数据挖掘涉及多种技术和方法,它融合了统计学、机器学习、数据库技术等多个领域的知识。以下是数据挖掘的一些关键概念和步骤:

数据挖掘的目标

  • 知识发现:从大量的数据中发现隐藏的、有效的、新颖的、潜在有用的信息和知识。
  • 预测建模:建立模型来预测未来的趋势或行为。
  • 描述性建模:总结数据的主要特征,以便更好地理解数据集。

数据挖掘的过程

  1. 业务理解:明确业务目标,确定需要解决的问题。
  2. 数据理解:收集和探索数据,理解其结构和内容。
  3. 数据准备:清洗数据,处理缺失值、异常值,转换数据格式等。
  4. 建模:应用统计和机器学习算法来构建模型。
  5. 评估:验证模型的有效性和实用性。
  6. 部署:将模型应用于实际业务场景,获取商业价值。

数据挖掘的技术

  • 分类:预测数据属于某个特定类别的概率。
  • 回归:预测一个连续值的结果。
  • 聚类:将相似的数据点分组在一起。
  • 关联规则学习:发现数据集中项目的相关性或依赖性。
  • 异常检测:识别不符合预期模式的数据点。
  • 降维:减少数据的维度,简化分析过程。

应用领域

数据挖掘广泛应用于各个行业,包括但不限于金融(信用评分)、零售(购物篮分析)、医疗(疾病预测)、电信(客户流失预测)、营销(客户细分)等。

工具与平台

有许多工具和技术可用于数据挖掘,如Python的Pandas、NumPy、SciPy、Scikit-Learn,R语言,SQL数据库,以及专门的数据挖掘软件如Weka、RapidMiner、KNIME等。

数据挖掘是一个跨学科的领域,它不仅需要掌握技术知识,还需要具备一定的业务洞察力,以便能够正确解释挖掘出的知识并将其转化为实际的应用价值。

代码示例

  1. 分类 (Classification)
    分类是一种监督学习方法,任务是将数据分为预定义的类别。常见的算法包括决策树、支持向量机、K近邻等。

例子:用决策树进行分类
假设我们有一组关于鸢尾花的数据集,目标是根据花的特征预测它的种类。

# 导入必要的库
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 加载数据集
iris = load_iris()
X = iris.data  # 特征
y = iris.target  # 目标

# 拆分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 创建决策树分类器
clf = DecisionTreeClassifier()

# 训练模型
clf.fit(X_train, y_train)

# 预测测试集
y_pred = clf.predict(X_test)

# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f'决策树分类的准确率: {accuracy:.2f}')

运行结果如下:
在这里插入图片描述
2. 聚类 (Clustering)
聚类是一种无监督学习方法,任务是将数据点根据相似性分成若干簇。常见算法有K均值(K-Means)、层次聚类等。

例子:使用K-Means进行聚类
我们可以用K-Means算法将鸢尾花数据集分成三类。

# 导入必要的库
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

# 加载数据集
X = iris.data

# 创建K-Means模型,假设有3类
kmeans = KMeans(n_clusters=3, random_state=42)

# 训练模型
kmeans.fit(X)

# 获取聚类结果
y_kmeans = kmeans.predict(X)

# 可视化聚类结果
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, cmap='viridis')
plt.title('K-Means聚类结果')
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.show()

运行结果如下:
在这里插入图片描述

  1. 关联规则挖掘 (Association Rule Mining)
    关联规则挖掘的目的是找出数据集中项目之间的有趣关系,常用于市场购物篮分析。常见算法包括Apriori和FP-growth。

例子:使用Apriori算法进行关联规则挖掘
假设我们有一组购物交易数据,目标是找出产品之间的关联规则。

# 导入必要的库
from mlxtend.frequent_patterns import apriori, association_rules
import pandas as pd

# 构造一个简单的购物篮数据
data = {'牛奶': [1, 0, 1, 1, 0],
        '面包': [1, 1, 0, 1, 0],
        '啤酒': [0, 1, 1, 0, 1],
        '鸡蛋': [1, 0, 0, 1, 0],
        '苹果': [0, 1, 1, 0, 1]}
df = pd.DataFrame(data)

# 使用Apriori算法找到频繁项集
frequent_itemsets = apriori(df, min_support=0.6, use_colnames=True)

# 生成关联规则
rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1.0)

print(frequent_itemsets)
print(rules)

运行结果如下:
在这里插入图片描述

  1. 异常检测 (Anomaly Detection)
    异常检测是识别数据中不符合预期模式的异常点的过程,常用于信用卡欺诈检测、网络安全等。

例子:使用Isolation Forest进行异常检测
我们可以使用Isolation Forest来检测数据集中的异常点。

# 导入必要的库
from sklearn.ensemble import IsolationForest
import numpy as np

# 生成随机数据,其中一部分是异常点
rng = np.random.RandomState(42)
X = 0.3 * rng.randn(100, 2)
X = np.r_[X + 2, X - 2]  # 正常点
X = np.r_[X, rng.uniform(low=-4, high=4, size=(20, 2))]  # 添加一些异常点

# 训练Isolation Forest模型
clf = IsolationForest(random_state=42)
clf.fit(X)

# 预测数据中的异常点
y_pred = clf.predict(X)

# 正常点为1,异常点为-1
print(y_pred)

运行结果如下:
在这里插入图片描述
5.降维
常用的降维方法包括主成分分析(PCA)、线性判别分析(LDA)和t-SNE等。

例子:使用PCA进行降维
假设我们有一个包含多个特征的二维数据集,我们可以使用PCA将数据从多维降到二维或者一维。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris

# 加载示例数据集(Iris数据集)
data = load_iris()
X = data.data  # 特征
y = data.target  # 标签

# 使用PCA进行降维(将数据从4维降到2维)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

# 可视化降维后的数据
plt.figure(figsize=(8, 6))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', edgecolor='k', s=100)
plt.title('PCA on Iris Dataset')
plt.xlabel('First Principal Component')
plt.ylabel('Second Principal Component')
plt.colorbar(label='Class')
plt.show()

运行结果如下:
在这里插入图片描述

总结

智慧水务项目中需要对采集的数据进行预处理和挖掘特征,通过实时数据进行大数据处理,形成目标数据集,这是进行下一步的基础,故对数据挖掘也进行学习。

<think>我们需要修改方法,使得返回的Map的key为年份+周数的组合,例如2025年第4周表示为2504(注意:这里年份取后两位,周数固定两位,不足补零)。 但是注意:题目要求如2504表示2025年第4周,即年份取后两位(25)加上两位的周数(04),所以总共是4位数。 步骤: 1. 在分组的时候,我们仍然需要按周分组,但是为了后续构造key,我们还需要知道年份。不过注意,目标年份已经作为参数传入(targetYear),而且我们过滤的时候已经只取该年份的数据。 因此,周数对应的年份就是targetYear。 2. 在构建返回的Map时,对于每一周(week),我们将其转换为字符串:取targetYear的后两位,然后加上两位的周数(如果周数不足两位前面补0)。 3. 注意:周数是从1到53,所以当周数小于10时,需要补0。 4. 但是,我们也要注意,targetYear可能是一个四位数的年份(如2025),我们只需要后两位,即25。 5. 修改方法:将原来的key从周数(Integer)改为字符串(String)或者整数(Integer)?题目要求是2504,这是一个整数。但是注意,如果周数不足10,比如第4周,那么就是04,所以整个数字是2504。 但是,如果使用整数,那么04会变成4,所以2504实际上是2504(整数),而第10周就是2510,这样是可以的。但是,第4周表示为2504,整数就是2504,没有问题。所以我们可以使用整数作为key。 6. 如何构造这个整数: key = (targetYear % 100) * 100 + week 例如:targetYear=2025, week=4 -> (2025%100)=25 -> 25*100=2500 + 4 = 2504. targetYear=2025, week=12 -> 25*100+12=2512. 7. 但是注意:week的范围是1到53,所以最大是53,那么25*100+53=2553,这个整数是4位数,符合要求。 8. 因此,我们可以这样生成key。 9. 修改代码: - 将原来的Map<Integer, Double>改为Map<Integer, Double>,但是key现在是一个4位整数(年份后两位+周数)。 - 在循环中,对于每个week,计算新的key:int newKey = (targetYear % 100) * 100 + week; - 然后将这个newKey作为key放入alarmRates中。 10. 注意:如果targetYear是2000年以前,比如1999,那么后两位是99,那么第1周就是9901,这样也是4位数,没有问题。 11. 但是,如果targetYear是1900,那么后两位是00,那么第1周就是0001(即1),这样就会变成1位数?但是注意,我们计算的是整数,00*100+1=1。这样就会导致不同年份的周可能重复(比如2001年的第1周也是1)。所以这种方法有缺陷。 12. 重新考虑:题目要求如2504,但如果是2000年,那么应该是0004?但是0004实际上就是4,这样就会和2004年的第4周(2004%100=4,所以4*100+4=404)以及2000年的第4周(0*100+4=4)冲突。所以用整数表示会有问题。 13. 因此,我们使用字符串来表示:将年份后两位(两位,不足两位前面补0)和周数(两位,不足两位前面补0)拼接成字符串。但是题目要求是整数?题目说“2504表示2025年第4周”,这里2504是一个整数,但是实际上它也可以看作是一个字符串转换而来的整数。然而,当周数不足10时,我们需要补0,这样整数就会是四位。但是,如果年份后两位是00,那么第1周就是0001,转换成整数就是1,这样就会丢失前导零,导致位数不足。 14. 为了避免冲突,我们有两种选择: a) 使用字符串:例如"2504"表示2025年第4周,"0001"表示2000年第1周。 b) 使用整数,但用更大的位数:比如用6位数,前四位是年份,后两位是周数。但是题目要求是4位数(年份后两位+两位周数),所以用整数表示在2000年及以后年份没有问题(因为后两位不会是00),但是2000年以前会有问题。 15. 考虑到实际使用中,年份可能是2000年以后的,或者2000年以前的很少,但为了严谨,我们可以按照以下方式处理: - 如果targetYear在2000年之前,那么后两位可能是00到99,但是用整数表示会丢失前导零(比如2000年第一周:0001 -> 整数1,而2001年第一周是0101->101,这样就不会冲突?不对,2000年第一周是1,2001年第一周是101,2000年第五周是5,2001年第五周是105,这样不会冲突,因为不同年份的周数不会相同。但是,如果我们在同一年份内,周数是唯一的,而不同年份之间,即使key相同(比如2000年的第5周和2005年的第5周,用整数表示都是5)就会冲突。所以整数表示法不可行。 16. 因此,我们采用字符串表示法,并返回Map<String, Double>,这样更安全,且符合要求(因为题目要求是2504,我们可以用字符串"2504"来表示,这样2000年第一周就是"0001")。 17. 修改: 将返回类型改为Map<String, Double> 在循环中,构造key:String key = String.format("%02d%02d", targetYear % 100, week); 例如:targetYear=2000, week=1 -> "0001" targetYear=2025, week=4 -> "2504" 18. 但是注意,如果周数超过99?不会,因为周数最大53,所以两位足够。 19. 因此,修改后的代码: 20. 另外,分组的时候我们不需要改变,因为分组还是按周分组,然后我们只对当前年份的数据进行处理。 21. 修改后的方法如下:</think>### 修改为年份+周数组合键的实现 #### 修改后的方法实现 ```java import java.util.*; import java.time.temporal.WeekFields; import java.util.stream.Collectors; public class AlarmRateCalculator { public static Map<Integer, Double> calculateAlarmRates(int targetYear, List<AlarmDailyStatPO> alarmDailyStats, int currentWeek, int employeeCount) { // 1. 按周分组统计报警总数 Map<Integer, Integer> weeklyAlarms = alarmDailyStats.stream() .filter(po -> po.getStatDate().getYear() == targetYear) .collect(Collectors.groupingBy( po -> po.getStatDate().get(WeekFields.ISO.weekOfWeekBasedYear()), Collectors.summingInt(AlarmDailyStatPO::getTotalCount) )); // 2. 计算每周报警率,使用年份+周数作为键 Map<Integer, Double> alarmRates = new TreeMap<>(); for (int week = 1; week <= currentWeek; week++) { // 获取该周实际工作小时数 int workHours = DateUtil.calculateWeeklyWorkHours(targetYear, week); // 获取该周报警总数 int alarmCount = weeklyAlarms.getOrDefault(week, 0); // 计算报警率(避免除零错误) double rate = (workHours > 0) ? (alarmCount * 100.0) / (employeeCount * workHours) : 0.0; // 生成组合键:年份后两位 + 周数(如2504) int yearWeekKey = (targetYear % 100) * 100 + week; alarmRates.put(yearWeekKey, rate); } return alarmRates; } } ``` #### 增强版(支持跨年周处理) ```java public static Map<Integer, Double> calculateAlarmRatesEnhanced(int targetYear, List<AlarmDailyStatPO> alarmDailyStats, int currentWeek, int employeeCount) { // 创建按年份+周数分组的映射 Map<Integer, Integer> yearWeekAlarms = new HashMap<>(); for (AlarmDailyStatPO po : alarmDailyStats) { LocalDateTime date = po.getStatDate(); int year = date.getYear(); int week = date.get(WeekFields.ISO.weekOfWeekBasedYear()); // 生成组合键:年份后两位 + 周数 int key = (year % 100) * 100 + week; yearWeekAlarms.put(key, yearWeekAlarms.getOrDefault(key, 0) + po.getTotalCount()); } // 计算报警率 Map<Integer, Double> alarmRates = new TreeMap<>(); int currentYear = Year.now().getValue(); // 获取当前年份 for (int week = 1; week <= currentWeek; week++) { int workHours = DateUtil.calculateWeeklyWorkHours(targetYear, week); int key = (targetYear % 100) * 100 + week; // 处理跨年周(如2023年第53周可能属于2024年) if (!yearWeekAlarms.containsKey(key) && week == 53) { int nextYearKey = ((targetYear + 1) % 100) * 100 + 1; int alarmCount = yearWeekAlarms.getOrDefault(nextYearKey, 0); alarmRates.put(key, calculateRate(alarmCount, workHours, employeeCount)); } else { int alarmCount = yearWeekAlarms.getOrDefault(key, 0); alarmRates.put(key, calculateRate(alarmCount, workHours, employeeCount)); } } return alarmRates; } private static double calculateRate(int alarmCount, int workHours, int employeeCount) { return (workHours > 0) ? (alarmCount * 100.0) / (employeeCount * workHours) : 0.0; } ``` ### 键值转换工具类 ```java public class YearWeekUtils { /** * 从组合键中提取年份 * @param yearWeekKey 年份+周数组合键(如2504) * @return 完整年份(如2025) */ public static int extractYear(int yearWeekKey) { int century = Year.now().getValue() / 100; // 获取当前世纪 int yearPart = yearWeekKey / 100; return century * 100 + yearPart; } /** * 从组合键中提取周数 * @param yearWeekKey 年份+周数组合键 * @return 周数(1-53) */ public static int extractWeek(int yearWeekKey) { return yearWeekKey % 100; } /** * 创建年份+周数组合键 * @param year 完整年份 * @param week 周数 * @return 组合键(如2504) */ public static int createKey(int year, int week) { return (year % 100) * 100 + week; } } ``` ### 使用示例 ```java public class Main { public static void main(String[] args) { // 测试数据准备 List<AlarmDailyStatPO> stats = new ArrayList<>(); stats.add(createPO(2025, 1, 10)); // 2025年第1周 stats.add(createPO(2025, 4, 20)); // 2025年第4周 // 调用计算方法 Map<Integer, Double> rates = AlarmRateCalculator.calculateAlarmRates( 2025, stats, 5, 100); // 结果解析 for (Map.Entry<Integer, Double> entry : rates.entrySet()) { int key = entry.getKey(); int year = YearWeekUtils.extractYear(key); int week = YearWeekUtils.extractWeek(key); System.out.printf("周键: %d → 年份: %d, 周数: %d, 报警率: %.2f%%%n", key, year, week, entry.getValue()); } /* 输出示例: 周键: 2501 → 年份: 2025, 周数: 1, 报警率: 0.10% 周键: 2502 → 年份: 2025, 周数: 2, 报警率: 0.00% 周键: 2503 → 年份: 2025, 周数: 3, 报警率: 0.00% 周键: 2504 → 年份: 2025, 周数: 4, 报警率: 0.20% 周键: 2505 → 年份: 2025, 周数: 5, 报警率: 0.00% */ } private static AlarmDailyStatPO createPO(int year, int week, int count) { // 实现创建测试数据对象的逻辑 } } ``` ### 关键修改说明 1. **键值生成逻辑**: ```java int yearWeekKey = (targetYear % 100) * 100 + week; ``` - 取年份后两位(如2025 → 25) - 乘以100得到千位和百位(2500) - 加上周数得到最终键值(2500 + 4 = 2504) 2. **跨年周处理**: - 第53周可能属于下一年(ISO周标准) - 特殊处理:当周数为53时检查下一年第1周的数据 3. **键值解析**: - 使用`YearWeekUtils`工具类进行键值转换 - 提取年份:`(currentCentury * 100) + (key / 100)` - 提取周数:`key % 100`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值