Java DataFrame解决方案

虽然Java社区没有像Python Pandas或Scala Spark DataFrame那样拥有一个绝对统治地位的“标准”DataFrame库,但有一些非常优秀且功能强大的库可以满足您的需求。

目前来看,最接近Pandas或R DataFrames体验的Java库是 Tablesaw

Tablesaw

Tablesaw 是一个用Java编写的、内存中的表格数据处理库。它提供了类似DataFrame的结构,支持数据加载、清理、转换、过滤、聚合、连接以及一些基本的统计和可视化功能。它的设计理念就是为了让Java开发者能够方便地进行数据科学工作。

优点:

缺点:

Tablesaw 的基本用法示例:

  1. 添加依赖 (Maven):

    <dependency>
        <groupId>com.github.lwhite1</groupId>
        <artifactId>tablesaw-core</artifactId>
        <version>0.43.1</version> <!-- 检查最新版本 -->
    </dependency>
    
    
  2. 代码示例:
    假设您的JSON数据已经被解析成Java对象列表,或者您可以从CSV等文件加载。这里我们以从CSV文件加载为例,或者您可以将JSON数据转换为List of Maps,然后手动构建Table。

    import tech.tablesaw.api.Table;
    import tech.tablesaw.api.StringColumn;
    import tech.tablesaw.api.DoubleColumn;
    import tech.tablesaw.columns.Column;
    import tech.tablesaw.joining.JoinType;
    import tech.tablesaw.selection.Selection;
    
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    public class TablesawExample {
    
        public static void main(String[] args) throws IOException {
            // 示例数据 (假设这是从您的JSON解析后的数据)
            List<Map<String, String>> jsonData = List.of(
                Map.of("st_name", "35kV响水浪变", "dev_name", "0460开关", "name", "Y时限失压闭锁", "status", "0"),
                Map.of("st_name", "35kV响水浪变", "dev_name", "0460开关", "name", "闭锁总", "status", "0"),
                Map.of("st_name", "110kV茂兰变", "dev_name", "10kV茂洞线43号杆0430开关", "name", "过负荷告警", "status", "1"),
                Map.of("st_name", "110kV茂兰变", "dev_name", "10kV茂洞线43号杆0430开关", "name", "开关事故总", "status", "1"),
                Map.of("st_name", "35kV响水浪变", "dev_name", "0460开关", "name", "Y时限失压闭锁", "status", "1")
            );
    
            // 1. 从List<Map>创建Table
            Table table = Table.create("SensorData");
            // 添加列
            StringColumn stNameCol = StringColumn.create("st_name");
            StringColumn devNameCol = StringColumn.create("dev_name");
            StringColumn nameCol = StringColumn.create("name");
            StringColumn statusCol = StringColumn.create("status"); // status 可以是String或Int
    
            for (Map<String, String> row : jsonData) {
                stNameCol.append(row.get("st_name"));
                devNameCol.append(row.get("dev_name"));
                nameCol.append(row.get("name"));
                statusCol.append(row.get("status"));
            }
    
            table.addColumns(stNameCol, devNameCol, nameCol, statusCol);
    
            System.out.println("原始数据表:");
            System.out.println(table.print());
    
            // 2. 数据过滤 (类似 Pandas 的 df[df['st_name'] == '110kV茂兰变'])
            System.out.println("\n--- 过滤示例:查找 'st_name' 为 \"110kV茂兰变\" 的记录 ---");
            Table filteredTable = table.where(table.stringColumn("st_name").isEqualTo("110kV茂兰变"));
            System.out.println(filteredTable.print());
    
            // 3. 数据聚合 (类似 Pandas 的 df.groupby('st_name').count())
            System.out.println("\n--- 聚合示例:统计 'st_name' 的出现次数 ---");
            // 注意:Tablesaw的summarize方法通常需要一个SummaryFunction,这里我们用count
            // 我们可以先将st_name转换为一个可以计数的列,或者直接使用group().count()
            Table countsTable = table.group("st_name").count();
            System.out.println(countsTable.print());
    
            System.out.println("\n--- 聚合示例:统计 'name' 的出现次数 ---");
            Table nameCountsTable = table.group("name").count();
            System.out.println(nameCountsTable.print());
    
            // 4. 更复杂的聚合:计算特定设备 'dev_name' 下 'status' 为 '1' 的记录数量
            System.out.println("\n--- 聚合示例:计算 '10kV茂洞线43号杆0430开关' 下 'status' 为 '1' 的记录数量 ---");
            String devNameTarget = "10kV茂洞线43号杆0430开关";
            Table specificStatusCount = table.where(
                table.stringColumn("dev_name").isEqualTo(devNameTarget)
                .and(table.stringColumn("status").isEqualTo("1"))
            );
            System.out.println("设备 '" + devNameTarget + "' 下 'status' 为 '1' 的记录有: " + specificStatusCount.rowCount() + " 条");
    
            // 5. 转换为数值列进行统计分析 (如果status是数值)
            // 假设status可以转换为double,这里我们先创建一个新的Table来演示
            Table numericTable = Table.create("NumericSensorData");
            StringColumn stNameColN = StringColumn.create("st_name");
            DoubleColumn statusDoubleCol = DoubleColumn.create("status_numeric");
    
            for (Map<String, String> row : jsonData) {
                stNameColN.append(row.get("st_name"));
                try {
                    statusDoubleCol.append(Double.parseDouble(row.get("status")));
                } catch (NumberFormatException e) {
                    statusDoubleCol.appendMissing(); // 处理无法转换的情况
                }
            }
            numericTable.addColumns(stNameColN, statusDoubleCol);
    
            System.out.println("\n--- 数值统计示例:按st_name计算status_numeric的平均值 ---");
            Table avgStatus = numericTable.summarize(numericTable.doubleColumn("status_numeric"), tech.tablesaw.aggregate.AggregateFunctions.mean).by("st_name");
            System.out.println(avgStatus.print());
        }
    }
    
    

其他 Java DataFrame 解决方案(作为了解)

  1. Joins (by jOOQ): jOOQ 是一个SQL构建器,但其作者也开发了一个名为 Joins 的库,它提供了一些类似DataFrame的功能,特别擅长处理关系型数据和执行连接操作。它更偏向于SQL风格的数据操作。
  2. DataFrames.java (by Apache Commons): 这是Apache Commons项目下的一个实验性子项目,旨在提供Java的DataFrame实现。但目前来看,其活跃度和功能成熟度可能不如Tablesaw。
  3. Eclipse Collections: 虽然它不是一个专门的DataFrame库,但Eclipse Collections是一个非常强大的Java集合库,提供了大量高性能的集合类型和函数式编程工具。通过巧妙地组合其API,您可以实现很多类似DataFrame的数据转换和聚合操作,尤其适用于处理内存中的小型到中等数据集。

总结

如果您希望在Java中进行数据分析,Tablesaw 是一个非常好的起点。它提供了直观的API和强大的内存中数据处理能力,非常适合进行数据探索、清洗和初步的统计分析。

如果您需要处理的数据量非常大(超出单机内存),或者需要分布式计算能力,那么Spark仍然是主流选择,即使它有编译速度和环境配置的缺点。但对于大多数中小型数据集,Tablesaw足以胜任。