代码之家  ›  专栏  ›  技术社区  ›  Eduardo

Java:对两个或多个时间序列求和

  •  5
  • Eduardo  · 技术社区  · 8 年前

    我有多个时间序列:

           x
    |    date    | value |
    | 2017-01-01 |   1   |
    | 2017-01-05 |   4   |
    |     ...    |  ...  |
    
           y
    |    date    | value |
    | 2017-01-03 |   3   |
    | 2017-01-04 |   2   |
    |     ...    |  ...  |
    

    令人沮丧的是,在我的数据集中,两个系列中并不总是有匹配的日期。对于缺少一个可用日期的情况,我希望使用最后一个可用日期(如果没有可用日期,则使用0)。 e、 g代表 2017-01-03 我会使用 y=3 x=1 (从之前的日期)获取 output = 3 + 1 = 4

    我的每个时间序列的格式如下:

    class Timeseries {
        List<Event> x = ...;
    }
    
    class Event {
        LocalDate date;
        Double value;
    }
    

    并将其读入 List<Timeseries> allSeries

    我想我可以用流来求和

    List<TimeSeries> allSeries = ...
    Map<LocalDate, Double> byDate = allSeries.stream()
        .flatMap(s -> s.getEvents().stream())
    .collect(Collectors.groupingBy(Event::getDate,Collectors.summingDouble(Event::getValue)));
    

    但这不会有我上面提到的我错过日期的逻辑。

    我还能怎样做到这一点?(它不必通过流)

    3 回复  |  直到 8 年前
        1
  •  3
  •   daniu    8 年前

    我想说的是,您需要扩展Timeseries类以获得适当的查询函数。

    class Timeseries {
        private SortedMap<LocalDate, Integer> eventValues = new TreeMap<>();
        private List<Event> eventList;
    
        public Timeseries(List<Event> events) {
            events.forEach(e -> eventValue.put(e.getDate(), e.getValue());
            eventList=new ArrayList(events);
        }
        public List<Event> getEvents() {
            return Collections.unmodifiableList(eventList);
        }
    
        public Integer getValueByDate(LocalDate date) {
            Integer value = eventValues.get(date);
            if (value == null) {
                // get values before the requested date
                SortedMap<LocalDate, Integer> head = eventValues.headMap(date);
                value = head.isEmpty()
                    ? 0   // none before
                    : head.get(head.lastKey());  // first before
            }
            return value;
        }
    }
    

    然后合并

    Map<LocalDate, Integer> values = new TreeMap<>();
    List<LocalDate> allDates = allSeries.stream().flatMap(s -> s.getEvents().getDate())
        .distinct().collect(toList());
    
    for (LocalDate date : allDates) {
        for (Timeseries series : allSeries) {
            values.merge(date, series.getValueByDate(date), Integer::ad);
        }
    }
    

    编辑:实际上 NavigableMap 接口在这种情况下更为有用,它使丢失的数据成为可能

    Integer value = eventValues.get(date);
    if (value == null) {
        Entry<LocalDate, Integer> ceiling = eventValues.ceilingKey(date);
        value = ceiling != null ? eventValues.get(ceiling) : 0;
    }
    
        2
  •  1
  •   Tatu Lahtela    8 年前

    一种方法是按日期比较事件,并利用树集 floor 方法:

    class Event implements Comparable<Event> {
            // ... 
            @Override
            public int compareTo(Event o) {
                return date.compareTo(o.date);
            }
    }
    

    然后在Timeseries类中,而不是在列表中使用 TreeSet<Event> x 并用空条目填充 地板 如果没有以前的值,则返回该值:

    class Timeseries {
            public static final Event ZERO = new Event(LocalDate.of(1, 1, 1), 0d);
            TreeSet<Event> x = new TreeSet<>(Arrays.asList(ZERO));
    
            // ...
    }
    

    现在收集所有已知事件并计算总和:

     TreeSet<Event> events = allSeries.stream()
                    .flatMap(s -> s.getEvents().stream()).collect(Collectors.toCollection(TreeSet::new));
    
    
     Map<LocalDate, Double> sumsByDate = events.stream().
                    map(event -> new AbstractMap.SimpleEntry<>(event.getDate(),
                                                               allSeries.stream().mapToDouble(a -> a.getEvents().floor(event).getValue())
                                                                       .sum())).
                    filter(p -> !p.getKey().equals(Timeseries.ZERO.getDate())).
                    collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    
        3
  •  0
  •   Eduardo    8 年前

    所以我设法用streams部分实现了这一点。虽然您在 getRelevantValueFor 方法我希望有一个更有效的解决方案。

    public Timeseries combine(List<Timeseries> allSeries) {
    
        // Get a unique set of all the dates accross all time series
        Set<LocalDate> allDates = allSeries.stream().flatMap(t -> t.get().stream()).map(Event::getDate).collect(Collectors.toSet());
    
        Timeseries output = new Timeseries();
    
        // For each date sum up the latest event in each timeseries
        allDates.forEach(date -> {
            double total = 0;
            for(Timeseries series : allSeries) {
                total += getRelevantValueFor(series, date).orElse(0.0);
            }
            output.add(new Event(date, total));
        });
        return output;
    }
    
    private Optional<Double> getRelevantValueFor(Timeseries series, LocalDate date) {
        return series.getEvents().stream().filter(event -> !event.getDate().isAfter(date)).max(ascendingOrder()).map(Event::getValue);
    }
    
    private Comparator<Event> ascendingOrder() {
        return (event1, event2) -> {
            long diff = event1.getDate().toEpochMilli() - event2.getDate().toEpochMilli();
            if(diff>0) return 1;
            if(diff<0) return -1;
            return 0;
        };
    }