Java和Python是当今最流行的两种计算机语言。两者都非常成熟,并提供了一个工具和技术生态系统,帮助我们解决数据科学领域中出现的具有挑战性的问题。每种语言都有自己的长处,我们需要知道什么时候使用哪种工具,或者什么时候它们应该协同工作以相互补充。Python是一种动态类型的语言,使用起来非常简单,如果我们不想接触复杂的程序,绝对是进行复杂计算的首选语言。Python提供了优秀的库(Pandas、NumPy、Matplotlib、ScyPy、PyTorch、TensorFlow等)来支持对数据结构或数组的逻辑、数学和科学运算。Java是一种非常健壮的语言,具有强类型,因此具有更严格的语法规则,因此不易出现编程错误。与Python一样,它也提供了大量库来处理数据结构、线性代数、机器学习和数据处理(ND4J、Mahout、Spark、Deeplearning4J等)。本文将向您展示如何使用Java和Python对大量表格数据进行简单的数据分析并计算一些统计数据。我们可以查看使用每个平台进行数据分析的不同技术,比较它们的扩展方式,以及应用并行计算来提高其性能的可行性。提出问题我们想对不同州的大量城市的价格进行简单分析,假设我们有一个包含此信息的CSV文件。阅读文件并继续过滤掉一些州,并按城邦对剩余州进行分组以获得一些基本统计数据。人们希望找到一种解决方案,该解决方案能够随着输入数据的大小增长而高效执行和扩展。数据样本是:城市州基本价格实际价格LaJosePA34.1733.19PreachersSloughWA27,4690.17DoonanCornersNY92.0162.46DoonanCorners165sCatle597.WA4165sCatle97..16943.21MarbleRockIA97.13391.49MineralCA99.13289.37BlountvilleIN92.50557.66BlountsvilleIN122.50557.66CoeIN187.85943.98CeciliaKY92.85273.61目标是展示如何使用Java和Python解决这些类型的问题。该示例非常简单且范围有限,但可以轻松扩展到更具挑战性的问题。Java方法首先定义了一个封装数据元素的Java记录:recordInputEntry(Stringcity,Stringstate,doublebasePrice,doubleactualPrice){}Record(record)是JDK14引入的一种新的类型声明,是一种简写方式来定义提供构造函数、访问器、equals和哈希实现的不可变类。接下来,读取CVS文件并将它们添加到列表中:ListinputEntries=readRecordEntriesFromCSVFile(recordEntries.csv);要按城市和州对输入元素进行分组,请定义它:recordCityState(Stringcity,Stringstate){};使用以下类封装属于一个组的所有元素的统计信息:recordStatsAggregation(StatsAccumulatorbasePrice,StatsAccumulatoractualPrice){}StatsAccumulator是Guava库的一部分。可以将double值的集合添加到类中,该类计算基本统计信息,例如计数、均值、方差或标准差。StatsAccumulator可以用来获取InputEntry的basePrice和actualPrice的统计信息。现在我们有了解决问题的所有材料。JavaStreams为数据操作和分析提供了一个强大的框架。其声明式编程风格支持选择、过滤、分组和聚合,简化了数据操作和统计分析。它的框架还提供了一个健壮的实现,可以处理大型(甚至是无限的)流,并通过使用并行、惰性和短路来高效地处理。所有这些特性使JavaStreams成为解决此类问题的绝佳选择。实现非常简单:Mapstats=inputEntries.stream().filter(i->!(i.state().equals("MN")||i.state().equals("CA"))).collect(groupingBy(entry->newCityState(entry.city(),entry.state()),collectingAndThen(Collectors.toList(),list->{StatsAccumulatorsac=newStatsAccumulator();sac.addAll(list.stream().mapToDouble(InputEntry::basePrice));StatsAccumulatorsas=newStatsAccumulator();sas.addAll(list.stream().mapToDouble(InputEntry::actualPrice));返回新的StatsAggregation(sac,SAS);})));在代码的第2行,我们使用Stream::filter。这是一个布尔函数,用于过滤列表中的元素。可以实施lambda表达式来删除任何包含“MN”或“CA”状态的元素。然后它继续收集列表的元素并调用Collectors::groupingBy()(第3行),它接受两个参数:一个排序函数,使用CityState记录按城市和州进行分组(第3行)。包含属于同一的元素的下游收集器。使用Collectors::collectingAndThen(第4行),它接受两个参数并分两步进行归约:我们使用Collectors::toList(第4行),它返回一个收集器,该收集器将属于同一个数组的元素放在列表中。?该列表随后进行了排序和转换。使用lambda函数(第5到9行)定义两个StatsAccumulator,其中分别为前一个列表的basePrice和actualPrice元素计算统计数据。最后,返回包含这些元素的新创建的StatsAggregation记录。如前所述,使用JavaStreams的优点之一是它提供了一种使用多线程进行并行处理的简单机制。这允许通过利用CPU的多核资源同时执行多个线程。只需在流中添加一个“并行”:Mapstats=inputEntries.stream().parallel()。这导致流框架将元素列表细分为多个部分,并在单独的线程中同时运行它们。当所有不同的线程完成它们的计算时,框架将它们串行添加到生成的Map中。在第4行中使用Collectors::groupingByConcurrent而不是Collectors:groupingBy。在这种情况下,框架使用并发映射,允许将来自不同线程的元素直接插入到该映射中,而无需串行组合。有了这三种可能性,就可以检查它们如何执行之前的统计计算(不包括从CSV文件加载数据的时间),因为负载从500万翻倍到2000万:SerialParallelParallel&GroupByConcurrent5百万个元素3.045seconds1.941seconds1.436seconds一千万个元素6.405seconds2.876seconds2.785seconds二千万个元素8.507seconds4.956seconds4.537seconds可以看到并行运行大大提高了性能;切一半。使用GroupByConcurrent可额外赚取10%。最后,得到结果是微不足道的;例如,要获取印第安纳州布朗茨维尔的统计数据,我们只需要:StatsAggregationaggregate=stateAggr.get(newCityState("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());得到的结果:Blountsville:INbasePrice.平均值:50.302588996763795basePrice.sampleVariance:830.7527439246837basePrice.sampleStandardDeviation:28.822781682632293basePrice.count:309basePrice.min:0.56basePrice.max:99.59actualPrice.mean:508.8927831715211actualPrice.sampleVariance:78883.35878833274actualPrice.sampleStandardDeviation:280.86181440048546actualPrice.count:309actualPrice.min:0.49actualPrice.max:999.33Python的方法在Python中,有几个库可以处理数据统计和分析其中,Pandas库非常适合处理大量Tabulardata,whichprovidesveryefficientfiltering,grouping,andstatisticalanalysismethods.UsePythontoanalyzepreviousdata:importpandasaspddefgroup_aggregations(df_group_by):df_result=df_group_by.agg({'basePrice':['count','min','max','mean','std','var'],'actualPrice':['count','min','max','mean','std','var']})returndf_resultif__name__=='__main__':df=pd.read_csv("recordEntries.csv")excluded_states=['MN','CA']df_st=df.loc[~df['state'].isin(excluded_states)]group_by=df_st.groupby(['city','state'],sort=False)aggregated_results=group_aggregations(group_by)Inthemainsection,firstcallpandas.read_csv()(line11)toloadthecomma-separatedvalues??fromthefileintoaPandasDataFrame.Online13,use~df['state'].isin(excluded_states)togetaPandasSeriesofBooleanvalues,andusepandas.loc()tofilterthestates(MNandCA)thatareexcluded.接下来,使用第14行的DataFrame.groupby()按城市和州分组。结果由group_aggregations()处理,它保留每个组的ba??sePrice和actualPrice的统计信息。在Python中打印结果非常简单。IN和Blountsville的结果:print(aggregated_results.loc['Blountsville','IN']['basePrice'])print(aggregated_results.loc['Blountsville','IN']['actualPrice'])统计数据:base_price:Name:(Blountsville,IN),dtype:float64count309.000000min0.560000max99.590000mean50.302589std28.822782var830.752744actual_price:Name:(Blountsville,IN),dtype:float64count309.000000min0.490000max999.330000mean508.892783std280.861814var78883.358788为了并行运行In前面的代码,大家一定要记住Python不像Java那样支持细粒度的锁机制。必须解决全局解释器锁(GIL)的问题,无论你有多少CPU、多核或线程,一次只允许一个线程执行。为了支持并发,我们必须考虑到有一个CPU密集型进程,因此,最好的方法是使用多进程。所以需要修改代码:frommultiprocessingimportPoolimportpandasaspddefaggreg_basePrice(df_group):ct_st,grp=df_groupreturnct_st,grp.basePrice.agg(['count','min','max','mean','std','var'])如果__name__=='__main__':df=pd.read_csv("recordEntries.csv")start=time.perf_counter()excluded_states=['MN','CA']filtr=~df['state'].isin(excluded_states)df_st=df.loc[filtr]grouped_by_ct_st=df_st.groupby(['city','state'],sort=False)withPool()asp:list_parallel=p.map(aggreg_basePrice,[(ct_st,grouped)forct_st,groupedingrouped_by_ct_st])print(f'Timeelapsedparallel:{round(finish-start,2)}sec')和以前一样,使用Pandasgroupby()得到按城市和州分组数据(第14行)。在下一行中,使用多处理库提供的Pool()来映射分组数据和aggreg_basePrice来计算每个组的统计信息。Pool()将数据拆分并在几个并行的独立进程中进行统计计算。如下表所示,多处理比串行运行进程慢得多。所以对于这些类型的问题,不值得使用这种方法。还有另一种并发运行代码的方法——Modin。Modin提供了一种无缝的方式来并行化您的代码,这在您必须处理大量数据时非常有用。将import语句从importpandasaspd更改为importmodin.pandasaspd允许您并行运行代码并利用环境中可能存在的内核集群来加速代码执行。下表是刚刚涉及的不同场景的运行时间(同上,不包括从CSV文件中读取数据的时间):SerialMulti-ProcessModinProcess五百万个元素1.94秒20.25秒6.99秒千万个4.07秒25.1秒12.88秒2000万个元素7.62秒36.2秒25.94秒根据表格,在Python中串行运行代码甚至比在Java中更快。但是,使用多个进程会显着降低性能。使用Moding可以改善结果并使串行运行流程更有好处。值得一提的是,和之前一样,我们在计算时间的时候没有把从CSV文件中读取数据的时间计算在内。可以看出,对于Pandas中的CPU密集型进程,将代码并行化没有优势。从某种意义上说,这反映了Pandas最初的架构方式。Pandas在串行模式下的速度非常快,即使在处理大量数据时也能很好地扩展。需要指出的是,Python中统计的计算速度取决于它是如何执行的。为了获得快速计算,需要将其应用于统计函数。例如,执行一个简单的pandas.DataFrame.describe()来获取统计信息会运行得非常慢。Java的Streams或Python的Pandas是对大量数据进行分析和统计的两个绝佳选择。两者都有非常可靠的框架,并为出色的性能和可扩展性提供了足够的支持。Java提供了一个非常强大的基础设施,非常适合处理复杂的程序流。它非常高效,可以有效地并行运行进程。适合快速出结果。Python非常适合做数学和统计。它非常简单,相当快,并且非常适合进行复杂的计算。译者介绍翟可,51CTO社区编辑,目前在杭州从事软件研发,从事过电商、征信等系统工作。他享受分享知识的过程,丰富自己的生活。原标题:DataStatisticsandAnalysisWithJavaandPython,作者:ManuBarriola