【问题标题】:Finding a mode with decreasing precision寻找精度降低的模式
【发布时间】:2011-07-16 19:45:11
【问题描述】:

我觉得应该有一个可用的库来更简单地做两件事,A)在双精度的情况下找到数组的模式和 B)优雅地降低精度直到达到特定频率。

想象一下这样的数组:

double[] a = {1.12, 1.15, 1.13, 2.0, 3.4, 3.44, 4.1, 4.2, 4.3, 4.4};

如果我正在寻找频率 3,那么它将从小数点后 2 位到小数点后 1 位,最后返回 1.1 作为我的模式。如果我的频率要求为 4,它将返回 4 作为我的模式。

我确实有一组代码按我想要的方式工作,并返回我所期望的,但我觉得应该有一种更有效的方法来实现这一点,或者现有的库可以帮助我完成相同的。附件是我的代码,我会对我应该采取的不同方法的想法/cmets 感兴趣....我列出了迭代以限制精度降低的程度。

public static double findMode(double[] r, int frequencyReq)
{
    double mode = 0d;
    int frequency = 0;
    int iterations = 4;

    HashMap<Double, BigDecimal> counter = new HashMap<Double, BigDecimal>();

    while(frequency < frequencyReq && iterations > 0){
        String roundFormatString = "#.";
        for(int j=0; j<iterations; j++){
            roundFormatString += "#";
        }
        DecimalFormat roundFormat = new DecimalFormat(roundFormatString);
        for(int i=0; i<r.length; i++){

            double element = Double.valueOf(roundFormat.format(r[i]));

            if(!counter.containsKey(element))
                counter.put(element, new BigDecimal(0));

            counter.put(element,counter.get(element).add(new BigDecimal(1)));
        }

        for(Double key : counter.keySet()){

            if(counter.get(key).compareTo(new BigDecimal(frequency))>0){
                mode = key;
                frequency = counter.get(key).intValue();
                log.debug("key: " + key + " Count: " + counter.get(key));
            }
        }
        iterations--;
    }

    return mode;
}

编辑

根据 Paulo 的评论,另一种表述问题的方式:目标是找到一个数字,其中邻域中至少有 frequency 数组元素,邻域的半径尽可能小。

【问题讨论】:

  • 只是要明确一点,mode 是最常用的元素,是吗?
  • 正确,虽然我从技术上意识到,一旦我开始降低精度,它并不完全是模式,而是一个近似值。
  • 所以我们搜索邻域中至少有frequency数组元素的数字,并且邻域的半径尽可能小。这可能是对问题的重新表述吗?
  • @Paulo,是的,我认为这是改写它的好方法。我会将其编辑到问题中。

标签: java algorithm statistics mode


【解决方案1】:

我认为您的代码没有任何问题,而且我怀疑您是否会找到一个执行如此特定功能的库。但是,如果您仍然想要一个想法来解决这个问题,使用一种更 OOP 的方法来重用 Java 集合,那么这里有另一种方法:

  • 创建一个类来表示具有不同小数位数的数字。它会有类似 VariableDecimal(double d,int ndecimals) 的构造函数。
  • 在该类中覆盖对象方法equalshashCode。您的equals 实现将测试VariableDecimal 的两个实例是否相同,同时考虑到值d 和小数位数。 hashCode 可以简单地返回 d*exp(10,ndecimals) 转换为整数。

在您的逻辑中使用 HashMaps 以便他们重用您的对象:

HashMap<VariableDecimal, AtomicInteger> counters = new HashMap<VariableDecimal, AtomicInteger>();
for (double d : a) {
     VariableDecimal vd = new VariableDecimal(d,ndecimals);
     if (counters.get(vd)!=null)
         counters.set(vd,new AtomicInteger(0));
     counters.get(vd).incrementAndGet();

}
/* at the end of this loop counters should hold a map with frequencies of 
   each double for the selected precision so that you can simply traverse and 
   get the max */

这段代码没有显示减少小数位数的迭代,这是微不足道的。

【讨论】:

    【解决方案2】:

    这里是重新表述的问题的解决方案:

    目标是找到一个数字,其中邻域中至少有frequency 数组元素,并且邻域的半径尽可能小。

    (我在输入数组中随意切换1.151.13的顺序。)

    基本思想是:我们已经对输入进行了排序(即相邻元素是连续的),并且我们知道我们想要在我们的邻域中有多少元素。所以我们在这个数组上循环一次,测量左边元素和右边元素frequency元素之间的距离。它们之间是frequency 元素,所以这形成了一个邻域。然后我们简单地取最小这样的距离。 (我的方法返回结果的方式比较复杂,你可能想做得更好。)

    这并不完全等同于您的原始问题(不适用于固定的数字步长),但也许这是您真正想要的 :-)

    不过,您必须找到一种更好的方法来格式化结果。

    package de.fencing_game.paul.examples;
    
    import java.util.Arrays;
    
    /**
     * searching of dense points in a distribution.
     *
     * Inspired by http://stackoverflow.com/questions/5329628/finding-a-mode-with-decreasing-precision.
     */
    public class InpreciseMode {
    
        /** our input data, should be sorted ascending. */
        private double[] data;
    
        public InpreciseMode(double ... data) {
            this.data = data;
        }
    
    
        /**
         * searchs the smallest neighbourhood (by diameter) which
         * contains at least minSize elements.
         *
         * @return an array of two arrays:
         *     {   { the middle point of the neighborhood,
         *           the diameter of the neighborhood  },
         *        all the elements of the neigborhood }
         *
         * TODO: better return an object of a class encapsuling these.
         */
        public double[][] findSmallNeighbourhood(int minSize) {
            int currentLeft = -1;
            int currentRight = -1;
            double currentMinDiameter = Double.POSITIVE_INFINITY;
    
            for(int i = 0; i + minSize-1 < data.length; i++) {
                double diameter = data[i+minSize-1] - data[i];
                if(diameter < currentMinDiameter) {
                    currentMinDiameter = diameter;
                    currentLeft = i;
                    currentRight = i + minSize-1;
                }
            }
            return
                new double[][] {
                { 
                    (data[currentRight] + data[currentLeft])/2.0,
                    currentMinDiameter
                },
                Arrays.copyOfRange(data, currentLeft, currentRight+1)
            };
        }
    
        public void printSmallNeighbourhoods() {
            for(int frequency = 2; frequency <= data.length; frequency++) {
                double[][] found = findSmallNeighbourhood(frequency);
    
                System.out.printf("There are %d elements in %f radius "+
                                  "around %f:%n     %s.%n",
                                  frequency, found[0][1]/2, found[0][0],
                                  Arrays.toString(found[1]));
            }
        }
    
    
        public static void main(String[] params) {
            InpreciseMode m =
                new InpreciseMode(1.12, 1.13, 1.15, 2.0, 3.4, 3.44, 4.1,
                                  4.2, 4.3, 4.4);
            m.printSmallNeighbourhoods();
        }
    
    }
    

    输出是

    There are 2 elements in 0,005000 radius around 1,125000:
         [1.12, 1.13].
    There are 3 elements in 0,015000 radius around 1,135000:
         [1.12, 1.13, 1.15].
    There are 4 elements in 0,150000 radius around 4,250000:
         [4.1, 4.2, 4.3, 4.4].
    There are 5 elements in 0,450000 radius around 3,850000:
         [3.4, 3.44, 4.1, 4.2, 4.3].
    There are 6 elements in 0,500000 radius around 3,900000:
         [3.4, 3.44, 4.1, 4.2, 4.3, 4.4].
    There are 7 elements in 1,200000 radius around 3,200000:
         [2.0, 3.4, 3.44, 4.1, 4.2, 4.3, 4.4].
    There are 8 elements in 1,540000 radius around 2,660000:
         [1.12, 1.13, 1.15, 2.0, 3.4, 3.44, 4.1, 4.2].
    There are 9 elements in 1,590000 radius around 2,710000:
         [1.12, 1.13, 1.15, 2.0, 3.4, 3.44, 4.1, 4.2, 4.3].
    There are 10 elements in 1,640000 radius around 2,760000:
         [1.12, 1.13, 1.15, 2.0, 3.4, 3.44, 4.1, 4.2, 4.3, 4.4].
    

    【讨论】:

    • 这是一种非常巧妙的表述方式,@Paŭlo!现在是时候采用您的方法和我的初始方法并对其进行基准测试了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-09
    • 2018-12-28
    • 2022-11-02
    • 2014-08-05
    • 2015-05-15
    • 2020-03-18
    相关资源
    最近更新 更多