Spring Boot集成tablesaw插件

1. 什么是tablesaw

Tablesaw是一款Java的数据可视化库,主要包括两部分:

  • 数据解析库,主要用于家在数据,对数据进行操作(转化、过滤、汇总等),类比Python中的Pandas库;
  • 数据可视化库,将目标哦数据转化为可视化的图标,类比Python中的Matplotlib库

与Pandas不同的是,Tablesaw中的表格以列(column)为基本单位,因此大部份操作都是基于列进行的,当然也包括部分对航操作的函数,但是功能比较有限。

Tablesaw源码 Tablesaw官方文档

从官方文档中可以看到Tablesaw支持的导入和导出的类型:

Format Import Export
CSV (and other delimited text) Yes Yes
JSON Yes Yes
RDBMS (via JDBC) Yes
Fixed Width Text Yes Yes
Excel Yes
HTML Yes Yes

1.1 tablesaw 工程结构

  • aggregate: maven的项目父级项目,主要定义项目打包的配置
  • breakerx: tablesaw库的注册中心,主要注册表和列

  • core: tablesaw库的核心代码,主要是数据的加工处理操作:数据的追加、排序、分组、查询等

  • data: 项目测试数据目录
  • docs: 项目Markdown文档目录
  • docs-src: 项目文档源码目录,主要作用是生成markdown文档
  • excel: 解析Excel文件数据的子项目
  • html: 解析html文件数据的子项目
  • json: 解析json 文件数据的子项目
  • jsplot: 数据可视化的子项目,主要作用家在数据生成可视化图表
  • saw: tablessaw读写图表数据的子项目

2. 示例代码

使用Springboot继承tablesaw插件加工和处理而为数据,并可视化

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
    <artifactId>springBootDemo</artifactId>
    <groupId>com.ygb</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>tablesaw</artifactId>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>tech.tablesaw</groupId>
    <artifactId>tablesaw-core</artifactId>
    <version>0.43.1</version>
    </dependency>
    <dependency>
    <groupId>tech.tablesaw</groupId>
    <artifactId>tablesaw-jsplot</artifactId>
    <version>0.43.1</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!--mysql-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
    </dependency>
    </dependencies>
    </project>
  2. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server:
    port: 8088
    spring:
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/tablesaw?characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: xiaoyuge
  3. 读取csv数据

    1
    2
    3
    4
    5
    6
    @Before
    public void before() throws IOException {
    log.info("init some data");
    tornadoes = Table.read().csv("/Users/xiaoyuge/Desktop/teblesaw-test.csv");
    }

  1. 打印列名

    1
    2
    3
    4
    @Test
    public void columnNames() throws IOException {
    System.out.println(tornadoes.columnNames());
    }
  2. 查看shape

    1
    2
    3
    4
    @Test
    public void shape() throws IOException {
    System.out.println(tornadoes.shape());
    }
  1. 查看表结构

    1
    2
    3
    4
    @Test
    public void structure() throws IOException {
    System.out.println(tornadoes.structure().printAll());
    }
  2. 表结构过滤

    1
    2
    3
    4
    5
    6
    7
    @Test
    public void structurefilter() throws IOException {
    System.out.println( tornadoes
    .structure()
    .where(tornadoes.structure().stringColumn("Column Type").isEqualTo("DOUBLE")));

    }
  3. 数据预览

    1
    2
    3
    4
    5
    @Test
    public void previewdata() throws IOException {
    System.out.println(tornadoes.first(3));

    }
  4. 列操作

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void ColumnOperate() throws IOException {
    StringColumn month = tornadoes.dateColumn("Date").month();
    tornadoes.addColumns(month);
    System.out.println(tornadoes.first(3));
    tornadoes.removeColumns("State No");
    System.out.println(tornadoes.first(3));
    }
  5. 排序

    1
    2
    3
    4
    5
    @Test
    public void sort() throws IOException {
    tornadoes.sortOn("-Fatalities");
    System.out.println(tornadoes.first(20));
    }
  1. 汇总
    1
    2
    3
    4
    @Test
    public void summary() throws IOException {
    System.out.println( tornadoes.column("Fatalities").summary().print());
    }
  1. 数据过滤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Test
    public void filter() throws IOException {
    Table result = tornadoes.where(tornadoes.intColumn("Fatalities").isGreaterThan(0));
    result = tornadoes.where(result.dateColumn("Date").isInApril());
    result =
    tornadoes.where(
    result
    .intColumn("Width")
    .isGreaterThan(300) // 300 yards
    .or(result.doubleColumn("Length").isGreaterThan(10))); // 10 miles

    result = result.select("State", "Date");
    System.out.println(result);
    }
  2. 写入文件

    1
    2
    3
    4
    @Test
    public void write() throws IOException {
    tornadoes.write().csv("rev_tornadoes_1950-2014-test.csv");
    }
  1. 从mysql读取数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Test
    public void datafrommysql() throws IOException {
    Table table = jdbcTemplate.query("SELECT user_id,username,age from user_info", new ResultSetExtractor<Table>() {
    @Override
    public Table extractData(ResultSet resultSet) throws SQLException, DataAccessException {
    return Table.read().db(resultSet);
    }
    });
    System.out.println(table);
    }

3. 数据可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.ygb.tablesaw;

import java.io.IOException;
import tech.tablesaw.api.Table;
import tech.tablesaw.plotly.Plot;
import tech.tablesaw.plotly.api.BubblePlot;
import tech.tablesaw.plotly.components.Figure;

/** */
public class BubbleExample2 {

public static void main(String[] args) throws IOException {

Table wines = Table.read().csv("/Users/xiaoyuge/Desktop/tablesaw-test.csv");

Table champagne =
wines.where(
wines
.stringColumn("wine type")
.isEqualTo("Champagne & Sparkling")
.and(wines.stringColumn("region").isEqualTo("California")));

Figure figure =
BubblePlot.create(
"Average retail price for champagnes by year and rating",
champagne, // table namex
"highest pro score", // x variable column name
"year", // y variable column name
"Mean Retail" // bubble size
);

Plot.show(figure);
}
}

结果如下图:

4. 常用API

4.1 列操作

Tablesaw中的列包括很多数据类型,常用的包括:字符串列(StringColumn)、整型列(IntColumn)、浮点列(DoubleColumn)、日期时间列(DateTimeColumn)等,每一种列提供的API都大同小异

创建列

1
2
3
4
5
/*1. 创建一个空列*/
DoubleColumn column = DoubleColumn.create("column name");
/*2. 创建一个有初始值的列*/
double[] values = {1, 2, 3, 7, 9.44242, 11};
DoubleColumn column = DoubleColumn.create("column name", values);

添加、编辑和删除数据

1
2
3
4
5
6
7
8
9
10
11
12
13
/*添加数据*/
column.append(value)
/*编辑数据*/
doubleColumn.set(index, value);
/*删除数据*/
//方式1
column.setMissing(0);
column = column.removeMissing();
//方式2
//这里用到了后面过滤的api
column.setMissing(index);
Selection sl = column.isNotMissing();
column = column.where(sl);

通用API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
name()                  // 返回列名
type() // 返回列的数据类型,比如LOCAL_DATE
size() // 返回列的长度
isEmpty() // 返回列是否为空
first(n) and last(n) // 返回列前/后n个数
max() and min() // 返回列的最大值/最小值
top(n) and bottom(n) // 返回n个最大/最小的数
print() // 返回列的字符串类型,用于输出
copy() // 复制一份新列
unique() // 返回列中的元素并只出现一次
countUnique() // 返回列中元素的种类
void sortAscending() // 升序排列
void sortDescending() // 降序佩列
append(otherColumn) // 将其他列添加到这个列后面
removeMissing() // 返回去除missing元素后的列

筛选

1
2
double[] values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
DoubleColumn column = DoubleColumn.create("column name", values);
  1. 筛选出想要的列

    1
    2
    3
    4
    5
    //筛选的第一步操作是创建一个表达式,比如我想筛选出所有大于5的数:
    Selection limit = column.isGreaterThan(5);

    //然后使用where过滤
    DoubleColumn column_new = column.where(limit);
  2. 有条件的编辑数据

    1
    2
    3
    4
    5
    6
    7
    //比如我们想替换所有大于5的数为100,那么可以:
    Selection limit = doublecolumn.isGreaterThan(5);
    doublecolumn.set(limit, 100.0);

    //比如我们可以将所有缺失值设置为平均值:
    double avg = doubleColumn.mean();
    doubleColumn.set(doubleColumn.isMissing(), avg)

输出列

将列输出到终端显示:

1
System.out.println(doublecolumn.print());

4.2 表格操作

Tablesaw由大量创建、查询、操作、显示和保存表的方法,而且对表的许多操作都会返回其他表。例如:当要求一个表描述器结构时,它回返回一个包含列名、类型和顺序的新表

创建表格(导入文件)

1
2
3
4
5
6
7
8
9
10
/*方式一:添加列到表格中*/
Table t = Table.create("name", column1, column2, column3...)
//比如:
double[] values1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
DoubleColumn column1 = DoubleColumn.create("column1", values1);
double[] values2 = {2, 1, 4, 4, 5, 6, 6, 1, 8, 4};
DoubleColumn column2 = DoubleColumn.create("column2", values2);

/*方式二:从文件中导入*/
Table t = Table.read().csv("myFile.csv");

当我们想从文件中导入表格时,我建议采用以下方法:

1
2
Table.read().file(File file);
Table.read().file(String path);

采用这种方式可以既可以导入.csv格式的文件,也可以导入.xlsx和.xls格式的文件,但是这种方式不能指定导入文件的sheet,如果想要指定sheet,采用下面这种方法:

1
2
3
4
Table cap = Table.read().usingOptions(XlsxReadOptions
.builder(String path)
.sheetIndex(String sheet));

添加、删除和选择列

  1. 添加列
    1
    2
    3
    4
    5
    //可以向表中添加一列或者多列addColumns()方法
    t.addColumns(aColmun...)

    //还可以通过提供索引来指定在特定位置插入列:
    t.addColumn(3, aColumn);
    需要注意的是,新添加的列必须为空,或者与表中的其他列具有相同数量的元素
  1. 删除列

    删除一列或多列

    1
    2
    t.removeColumns(int... columnindexs);
    t.removecolumns(String columnNames);

    或者指定想要保存的列,删除其他列,但是这种操作会在原列上进行,如果想要选择某几列而不改变原始数据,建议使用下面的选择列:

    1
    2
    t.retainColumns(int... columnindexes)
    t.retainColumns(String... columnnames)
  2. 选择列

    当前官方文档中选择列给出的是select()函数,但是建议使用selectColumns(),因为前者不能通过指定index来选择列,而后者可以根据名称和index选择:

    1
    2
    Table t2 = t.selectColumns("column1");
    Table t2 = t.selectColumns(0);

    也可以通过在当前表格中指定不需要的列来创建新表格,这可能会节省一些键入时间,同样这个函数也可以用名称和index来指定列:

    1
    2
    Table t2 = t.rejectColumns("column1");
    Table t2 = t.rejectColumns(0);

    如果只选择一列,通常需要将返回的列转换为更具体的类型。例如:

    1
    DoubleColumn dc = t.doubleColumn();

拼接表格

拼接表格有两种形式,一种是横向拼接,也就是行数相同,增加列数;另一种是纵向拼接,也就是列数相同,增加行

1
2
3
4
/*1. 横向组合*/
Table result = t.concat(t2);
/*2. 纵向组合*/
Table result = t.append(t2);

需要注意的是,横向拼接时两个表格的列名不能有重复,纵向拼接时对应列的列名必须相同。

筛选

筛选是在列筛选的基础上进行的,我们以下面这个表格为例:

1
2
3
4
5
6
7
8
9
10
11
12
LocalDate currentdate = LocalDate.of(Integer.parseInt("2024"), 1, 1);
List<LocalDate> date_list = new ArrayList<LocalDate>();
for(int d=0;d<10;d++)
{
truedate_list.add(currentdate.plusDays(d));
}
DateColumn date = DateColumn.create("Time", date_list);
truetrue
double[] values = {1, 2, 3, 4, 5,6,7,8,9,10};
DoubleColumn value = DoubleColumn.create("value", values);
truetrue
Table t = Table.create("a",date,value);
1
2
3
4
5
6
7
8
9
10
11
12
13
           a            
Time | value |
------------------------
2024-01-01 | 1 |
2024-01-02 | 2 |
2024-01-03 | 3 |
2024-01-04 | 4 |
2024-01-05 | 5 |
2024-01-06 | 6 |
2024-01-07 | 7 |
2024-01-08 | 8 |
2024-01-09 | 9 |
2024-01-10 | 10 |

这里将筛选出2022年1月5日以后的数据。

1
2
3
4
5
6
7
8
//选择我们想要筛选的列
DateColumn date_column = t.dateColumn(0);

//创建一个约束的表达式
Selection limit = date_column.dayOfMonth().isGreaterThan(5);

//用where()函数进行筛选
Table t2 = t.where(limit);
1
2
3
4
5
6
7
8
           a            
Time | value |
------------------------
2022-01-06 | 6 |
2022-01-07 | 7 |
2022-01-08 | 8 |
2022-01-09 | 9 |
2022-01-10 | 10 |

筛选时我们也可以同时指定多个约束,比如我想要筛选出2022年1月5日以后并且value大于7的数据:

1
2
3
4
5
DateColumn date_column = t.dateColumn(0);
Selection limit1 = date_column.dayOfMonth().isGreaterThan(5);
DoubleColumn value_column = t.doubleColumn(1);
Selection limit2 = value_column.isGreaterThan(7);
Table t2 = t.where(limit1.and(limit2));
1
2
3
4
5
6
           a            
Time | value |
------------------------
2022-01-08 | 8 |
2022-01-09 | 9 |
2022-01-10 | 10 |

输出表格

将表格输出到终端显示

1
System.out.println(t.print());

导出表格

1
2
//表格只能导出为.csv格式
table.write().csv("filename.csv");

5. 技术分析

5.1 数据操作

Tablesaw基于DataFrame概念,允许用户以表格形式存储和操作数据。DataFrame可以看作是一种表格,包含行和列,每一列都有特定的数据类型,可以轻松地添加、删除列,或者根据条件筛选

5.2 API涉及

Tablesaw的API设计注重简介和一致性。大多数功能可以通过链式调用来实现,比如:

1
2
Table table = Csv.read("data.csv").usingColumns(ColTypes.STRING, ColTypes.DOUBLE);
table.filter(row -> row.getString("column1").equals("value"));

这种设计是的代码更加易读和编写

5.3 统计分析与可视化

Tablesaw内置了各种统计函数,例如计算平均值、中位数、标准差等,此外,还集成了JFreeChart,可以直接生成图标,便于数据可视化

1
2
StatSummary summary = table.stats();
PiePlot.plot(table.rowCount(), "ColumnA", summary.count());

5.4 扩展性

Tablesaw是模块化的,意味着只引入需要的部分,避免不必要的依赖,它还提供了插件系统,允许你自定义行为和添加新功能

6. 应用场景

  • 数据清洗与预处理: 快速导入数据并进行基本清洗

  • 快速原型开发: 对数据集进行实验性分析,验证假设

  • 教学与学习:在Java环境中教授数据分析原理

特点概述:

  1. 简单易用: 直观的API,使数据操作变简单
  1. 性能优化:设计时考虑到大规模数据处理的效率
  1. 多源数据支持: 支持csv、Excel,甚至数据库和网络数据源
  1. 强大统计功能: 内置的共计方法和可视化选项
  1. 模块化和可扩展性: 灵活的架构,易于扩展