开启您的互联网之旅 您身边的互联网专家
准备开展业务 创建网站 成为代理商
01 JAN

使用Java和Python进行数据统计和分析

Java 和 Python 是当今最流行的两种计算机语言。两者都非常成熟,并提供了工具和技术生态系统,帮助我们解决数据科学领域出现的挑战性问题。每种语言都各有优势,我们要知道什么时候应该使用哪种工具,或者什么时候它们应该协同工作相互补充。

Python 是一种动态类型语言,使用起来非常简单,如果我们不想接触复杂的程序,它肯定是进行复杂计算的首选语言。Python 提供了优秀的库(Pandas、NumPy、Matplotlib、ScyPy、PyTorch、TensorFlow 等)来支持对数据结构或数组的逻辑、数学和科学操作。

Java 是一种非常健壮的语言,具有强类型,因此有更严格的语法规则,所以不易出现程序错误。与Python一样,它也提供了大量的库来处理数据结构、线性代数、机器学习和数据处理(ND4J、Mahout、Spark、Deeplearning4J 等)。

本文将介绍如何对大量表格数据进行简单的数据分析,并使用 Java 和 Python 计算一些统计数据。我们可以看到使用各个平台进行数据分析的不同技术,对比它们的扩展方式,以及应用并行计算来提高其性能的可行性。

提出问题

我们要对不同州的一大批城市的价格做一个简单的分析,这里假设有一个包含此信息的 CSV 文件。阅读文件并继续过滤掉一些州,并将剩余的州按城市-州分组以进行一些基本统计。希望能够找到有效执行的解决方案,并且能够随着输入数据规模的增长而有良好的扩展。

数据样本是:

城市

基本价格

实际价格

La Jose

PA 

34.17

33.19

Preachers Slough

WA

27,46

90.17

Doonan Corners

NY

92.0

162.46

Doonan Corners

NY

97.45

159.46

Castle Rock

WA

162.16

943.21

Marble Rock

IA 

97.13

391.49

Mineral

CA

99.13

289.37

Blountville

IN

92.50

557.66

Blountsville

IN 

122.50

557.66

Coe

IN

187.85

943.98

Cecilia

KY

92.85

273.61

目的是展示如何使用 Java 和 Python 解决这些类型的问题。该示例非常简单且范围有限,但很容易拓展到更具挑战性的问题。

Java 的方法

首先定义一个封装数据元素的 Java 记录:

record InputEntry(String city, String state, double basePrice, double actualPrice) {}1.

记录(record)是 JDK 14 中引入的一种新型类型声明。它是定义提供构造函数、访问器、equals 和哈希实现的不可变类的一种简捷方式。

接下来,读取 CVS 文件并将它们增加到一个列表中:

List<InputEntry> inputEntries = readRecordEntriesFromCSVFile(recordEntries.csv);1.

为了按城市和州对输入的元素进行分组,将其定义:

record CityState(String city, String state) {};1.

使用以下类来封装属于一个组的所有元素的统计信息:

record StatsAggregation(StatsAccumulator basePrice, StatsAccumulator actualPrice) {}1.

StatsAccumulator是Guava 库的一部分。可以将双精度值集合添加到类中,它会计算基本统计数据,例如计数、平均值、方差或标准差。可以使用StatsAccumulator来获取InputEntry的basePrice和actualPrice的统计数据。

现在我们已经拥有了解决问题的所有材料。Java Streams提供了一个强大的框架来实现数据操作和分析。它的声明式编程风格,对选择、过滤、分组和聚合的支持,简化了数据操作和统计分析。它的框架还提供了一个强大的实现,可以处理大量的(甚至是无限的流),并通过使用并行性、懒惰性和短路操作来高效处理。所有这些特性使Java Streams成为解决这类问题的绝佳选择。实现非常简单:

Map<CityState, StatsAggregation> stats = inputEntries.stream().
    filter(i -> !(i.state().equals("MN") || i.state().equals("CA"))).collect(
        groupingBy(entry -> new CityState(entry.city(), entry.state()), 
                   collectingAndThen(Collectors.toList(), 
                                     list -> {StatsAccumulator sac = new StatsAccumulator();
                                                sac.addAll(list.stream().mapToDouble(InputEntry::basePrice));
                                              StatsAccumulator sas = new StatsAccumulator();
                                                sas.addAll(list.stream().mapToDouble(InputEntry::actualPrice));
                                               return new StatsAggregation(sac, sas);}
                                       )));1.2.3.4.5.6.7.8.9.10.

在代码的第 2 行,我们使用Stream::filter. 这是一个布尔值函数,用于过滤列表中的元素。可以实现一个 lambda 表达式来删除任何包含“MN”或“CA”状态的元素。

然后继续收集列表的元素并调用Collectors::groupingBy()(第 3 行),它接受两个参数:

  • 一个分类功能,使用CityState记录来做城市和州的分组(第3行)。

  • 下游的收集器,包含属于同一<城州>的元素。使用Collectors::collectingAndThen(第 4 行),它采用两个参数分两步进行归约:

·我们使用Collectors::toList(第 4 行),它返回一个收集器,它将属于同一<城州>的所有元素放到一个列表中。

·随后对这个列表进行了整理转换。使用一个lambda函数(第5行至第9行)来定义两个StatsAccumulator(s),在这里分别计算前一个列表中的basePrice和actualPrice元素的统计数据。最后,返回到新创建的包含这些元素的StatsAggregation记录。

正如前文所述,使用Java Streams的优势之一是,它提供了一种简单的机制,可以使用多线程进行并行处理。这允许利用CPU的多核资源,同时执行多个线程。只要在流中添加一个 "parallel":

Map<CityState, StatsAggregation> stats = inputEntries.stream().parallel().1.

这导致流框架将元素列表细分为多个部分,并同时在单独的线程中运行它们。随着所有不同的线程完成它们的计算,框架将它们串行添加到生成的 Map 中。

在第4行中使用Collectors::groupingByConcurrent而不是Collectors:groupingBy。在这种情况下,框架使用并发映射,允许将来自不同线程的元素直接插入到此映射中,而不必串行组合。

有了这三种可能性,可以检查它们如何执行之前的统计计算(不包括从 CSV 文件加载数据的时间),因为加载量从500万条翻倍到2000万条:


串行

平行

并行 & GroupByConcurrent

五百万个元素

3.045 秒

1.941 秒

1.436 秒

一千万个元素

6.405 秒

2.876 秒

2.785 秒

两千万个元素

8.507 秒

4.956 秒

4.537 秒

可以看到并行运行大大提高了性能;随着负载的增加,时间几乎减半。使用 GroupByConcurrent 还可额外获得 10% 的收益。

最后,得到结果是微不足道的;例如,要获得印第安纳州 Blountsville 的统计数据,我们只需要:

StatsAggregation aggreg = stateAggr.get(new CityState("Blountsville ", "IN"));
System.out.println("Blountsville, IN");
System.out.println("basePrice.mean: " + aggreg.basePrice().mean());
System.out.println("basePrice.populationVariance: " + aggreg.basePrice().populationVariance());
System.out.println("basePrice.populationStandardDeviation: " + aggreg.basePrice().populationStandardDeviation());
System.out.println("actualPrice.mean: " + aggreg.basePrice().mean());
System.out.println("actualPrice.populationVariance: " + aggreg.actualPrice().populationVariance());
System.out.println("actualPrice.populationStandardDeviation: " + aggreg.actualPrice().populationStandardDeviation());1.2.3.4.5.6.7.8.

得到的结果:

请您留言


联系方式

晋城市鼎峰网络科技有限公司

公司注册地址:山西省泽州县金村镇湛家村客运东站(义乌小商品城G89-92,G95-98,G101-104)

运营中心地址:晋城市城区吕匠路与红星东街辅路交叉口北200米正海大厦六楼

邮编:048000

电话:0356-2100068

服务热线:400-100-6992

E-mail :cto@jcdfkj.com

Copyright© 2018 晋城鼎峰网络科技有限公司 ALL RIGHTS RESERVED.版权所有 晋ICP备16000853号-6 晋公安网备案14050002000295号