用Java Steams創(chuàng)建數(shù)據(jù)透視表
譯文【51CTO.com快譯】這次讓我們來看看如何使用Java 8 Streams(流)來實現(xiàn)一張數(shù)據(jù)透視表。通常情況下,原始數(shù)據(jù)本身一般并不容易被人們所讀懂,因此我們需要進行一些數(shù)據(jù)聚合操作來辨別出原始數(shù)據(jù)中的各種規(guī)律模式。而數(shù)據(jù)透視表就是這樣一種工具,它運用聚合的方法來展示出各種可視的圖形和圖表。
在過往的文章中,我們曾展示過如何使用Java 8 Streams進行類似SQL的效果對原始數(shù)據(jù)進行切割與分解。而今天的文章就建立在那些例子之上,如果你覺得這些對你有些難度的話,我建議你先通過如下鏈接瀏覽那兩篇文章。
https://dzone.com/articles/java-streams-groupingby-examples
https://dzone.com/articles/using-java-collectors
如果你不喜歡使用這種“原始數(shù)據(jù)”的方法來創(chuàng)建一張數(shù)據(jù)透視表,而選擇使用Excel來實現(xiàn)的話,我為你提供了另外的備選方案,請參考如下鏈接:
http://www.novixys.com/blog/excel-pivot-table-using-apache-poi/
將CSV數(shù)據(jù)表示為POJO
我們用如下的POJO(簡單的Java對象,Plain Ordinary Java Object)來表示棒球運動員及其工資。
public class Player {
private int year;
private String teamID;
private String lgID;
private String playerID;
private int salary;
// defined getters and setters here
}
它的數(shù)據(jù)來自于一個簡單的CSV文件,該文件內(nèi)既沒有引用的字段,也沒有多行的字段,而且單個字段中還不存在著逗號。我們可以使用一個簡單的正則表達式模式來解析該CSV文件,并將數(shù)據(jù)加載到一個列表之中。其數(shù)據(jù)看起來如下所示,它一共有大約26428行:
yearID,teamID,lgID,playerID,salary
198***TL,NL,barkele01,870000
198***TL,NL,bedrost01,550000
198***TL,NL,benedbr01,545000
198***TL,NL,campri01,633333
198***TL,NL,ceronri01,625000
...
我們用類似于如下代碼的Streams加載CSV的數(shù)據(jù):
Pattern pattern = Pattern.compile(",");
try (BufferedReader in = new BufferedReader(new FileReader(filename));){
List<Player> players = in
.lines()
.skip(1)
.map(line -> {
String[] arr = pattern.split(line);
return new Player(Integer.parseInt(arr[0]),
arr[1],
arr[2],
arr[3],
Integer.parseInt(arr[4]));
})
.collect(Collectors.toList());
}
定義數(shù)據(jù)透視表列的類
我們使用下面的類來定義數(shù)據(jù)透視表的各個列容器的類。這些列是用于對數(shù)據(jù)進行分組的。如果你使用的是SQL的話,這些列將出現(xiàn)在“GROUP BY”的語句中。
public class YearTeam
{
public int year;
public String teamID;
public YearTeam(int year,String teamID) {
this.year = year;
this.teamID = teamID;
}
@Override
public boolean equals(Object other)
{
if ( other == null ) return false;
if ( this == other ) return true;
if ( other instanceof YearTeam ) {
YearTeam yt = (YearTeam)other;
if ( year == yt.year && teamID.equals(yt.teamID) )
return true;
}
return false;
}
@Override
public int hashCode()
{
int hash = 1;
hash = hash * 17 + year;
hash = hash * 31 + teamID.hashCode();
return hash;
}
@Override
public String toString()
{
StringBuilder sbuf = new StringBuilder();
sbuf.append('[').append(year).append(", ").append(teamID)
.append(']');
return sbuf.toString();
}
}
僅僅是為了方便起見,這些字段被定義為了“public(公有)”訪問屬性。而對于你自己的應(yīng)用程序,你完全可以將其設(shè)置為“private(私有)”屬性,并根據(jù)需要添加getter和/或setter。
這個類覆蓋重寫了equals()和hashCode(),因為它將在存儲一張Map(映射表)時被作為key(鍵)使用。
用Streams分組數(shù)據(jù)
我們在此讀取該CSV的數(shù)據(jù),為每一行創(chuàng)建一個POJO,并用上面所定義的透視表列的類來對數(shù)據(jù)進行分組。
Map<YearTeam,List<Player>> grouped = in
.lines()
.skip(1)
.map(line -> {
String[] arr = pattern.split(line);
return new Player(Integer.parseInt(arr[0]),
arr[1],
arr[2],
arr[3],
Integer.parseInt(arr[4]));
})
.collect(Collectors.groupingBy(x-> new YearTeam(x.getYear(), x.getTeamID())));
至此,這些數(shù)據(jù)已被正確地收集到了在一張Map(映射表)中,并按指定的列項進行了分組。
將數(shù)據(jù)透視表打印成CSV
讓我們將數(shù)據(jù)透視表里的數(shù)據(jù)打印成一個CSV文件,以便我們可以將其加載到Excel中用以比較。在打印數(shù)據(jù)的時候,我們用到了求和函數(shù)summingLong()。Java 8 Streams同樣也提供了averagingLong()函數(shù)讓你求出平均值。如果你需要一次性地快速獲取的話,那么summarizingLong()就能夠?qū)⑺行畔⒊尸F(xiàn)到你的面前,請盡情享用吧!
CSV列的頭部標題
我們使用teamID的各個數(shù)值來作為列的頭部標題。對它們的收集和打印操作,如下所示。我們這里使用TreeSet來對其進行字母順序的排列。
Set<String> teams = grouped
.keySet()
.stream()
.map(x -> x.teamID)
.collect(Collectors.toCollection(TreeSet::new));
System.out.print(',');
teams.stream().forEach(t -> System.out.print(t + ","));
System.out.println();
打印數(shù)據(jù)
這樣就創(chuàng)建并打印出來一張完整的數(shù)據(jù)透視表。對于團隊每年的總和,我們從運動員列表中提取出來,并執(zhí)行一個加總和打印的操作。
Set<Integer> years = grouped
.keySet()
.stream()
.map(x -> x.year)
.collect(Collectors.toSet());
years
.stream()
.forEach(y -> {
System.out.print(y + ",");
teams.stream().forEach(t -> {
YearTeam yt = new YearTeam(y, t);
List<Player> players = grouped.get(yt);
if ( players != null ) {
long total = players
.stream()
.collect(Collectors.summingLong(Player::getSalary));
System.out.print(total);
}
System.out.print(',');
});
System.out.println();
});
比較來自Excel的輸出
我們將該CSV文件加載到Excel中并輸出如下數(shù)據(jù):
用Excel自帶的數(shù)據(jù)透視表功能進行比較,可見這些數(shù)據(jù)是相同的。(如下所示,由于某種原因,列“MON”出現(xiàn)在了Excel的前端。也許這是諸多神奇“特性”中的一種吧。反正各個數(shù)值都是相同的。)
朋友們,這就是使用Java自帶的簡單Collections(類集)來創(chuàng)建數(shù)據(jù)透視表的一種方法。你可以通過它去發(fā)現(xiàn)更多酷炫的用途!
總結(jié)
數(shù)據(jù)透視表的確是一種十分有用的數(shù)據(jù)匯總工具。大多數(shù)的數(shù)據(jù)分析軟件,包括Excel都有用到它。在此,我們學(xué)會了如何使用Java 8 Streams來創(chuàng)建出相同的數(shù)據(jù)結(jié)構(gòu)。同時,我們也用到了分組和加總來實現(xiàn)該功能。
【原標題】Making Pivot Table Using Java Streams (作者: Jay Sridhar )
原文鏈接:https://dzone.com/articles/java-pivot-table-using-streams
【51CTO譯稿,合作站點轉(zhuǎn)載請注明原文譯者和出處為51CTO.com】