Skip to content

Commit 17962ef

Browse files
committedMay 18, 2015
Add QgsHistogram class for calculating numeric histograms from a
list of values or a vector layer's attribute.
1 parent 7091d3b commit 17962ef

File tree

7 files changed

+449
-0
lines changed

7 files changed

+449
-0
lines changed
 

‎python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
%Include qgsgeometry.sip
4444
%Include qgsgeometryvalidator.sip
4545
%Include qgsgeometrysimplifier.sip
46+
%Include qgshistogram.sip
4647
%Include qgsmaptopixelgeometrysimplifier.sip
4748
%Include qgsgml.sip
4849
%Include qgsgmlschema.sip

‎python/core/qgshistogram.sip

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/** \ingroup core
2+
* \class QgsHistogram
3+
* \brief Calculator for a numeric histogram from a list of values.
4+
*
5+
* \note Added in version 2.9
6+
*/
7+
8+
class QgsHistogram
9+
{
10+
%TypeHeaderCode
11+
#include "qgshistogram.h"
12+
%End
13+
14+
public:
15+
16+
QgsHistogram();
17+
18+
virtual ~QgsHistogram();
19+
20+
/** Assigns numeric source values for the histogram.
21+
* @param values list of doubles
22+
*/
23+
void setValues( const QList<double>& values );
24+
25+
/** Assigns numeric source values for the histogram from a vector layer's field or as the
26+
* result of an expression.
27+
* @param layer vector layer
28+
* @param fieldOrExpression field name or expression to be evaluated
29+
* @returns true if values were successfully set
30+
*/
31+
bool setValues( QgsVectorLayer* layer, const QString& fieldOrExpression );
32+
33+
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
34+
* determined by the inter-quartile range of values and the number of values.
35+
* @returns optimal width for bins
36+
* @see optimalNumberBins
37+
* @note values must first be specified using @link setValues @endlink
38+
*/
39+
double optimalBinWidth() const;
40+
41+
/** Returns the optimal number of bins for the source values, calculated using the
42+
* Freedman-Diaconis rule. The number of bins are determined by the inter-quartile range
43+
* of values and the number of values.
44+
* @returns optimal number of bins
45+
* @see optimalBinWidth
46+
* @note values must first be specified using @link setValues @endlink
47+
*/
48+
int optimalNumberBins() const;
49+
50+
/** Returns a list of edges for the histogram for a specified number of bins. This list
51+
* will be length bins + 1, as both the first and last value are also included.
52+
* @param bins number of bins
53+
* @return list of bin edges
54+
* @note values must first be specified using @link setValues @endlink
55+
*/
56+
QList<double> binEdges( int bins ) const;
57+
58+
/** Returns the calculated list of the counts for the histogram bins.
59+
* @param bins number of histogram bins
60+
* @return list of histogram counts
61+
* @note values must first be specified using @link setValues @endlink
62+
*/
63+
QList<int> counts( int bins ) const;
64+
65+
};
66+

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ SET(QGIS_CORE_SRCS
107107
qgsgeometryvalidator.cpp
108108
qgsgml.cpp
109109
qgsgmlschema.cpp
110+
qgshistogram.cpp
110111
qgslayerdefinition.cpp
111112
qgslabel.cpp
112113
qgslabelattributes.cpp
@@ -510,6 +511,7 @@ SET(QGIS_CORE_HDRS
510511
qgsfontutils.h
511512
qgsgeometry.h
512513
qgsgeometrycache.h
514+
qgshistogram.h
513515
qgslayerdefinition.h
514516
qgslabel.h
515517
qgslabelattributes.h

‎src/core/qgshistogram.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/***************************************************************************
2+
qgshistogram.cpp
3+
----------------
4+
begin : May 2015
5+
copyright : (C) 2015 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgshistogram.h"
19+
20+
#include "qgsstatisticalsummary.h"
21+
#include "qgsvectorlayer.h"
22+
#include <qmath.h>
23+
24+
QgsHistogram::QgsHistogram()
25+
: mMax( 0 )
26+
, mMin( 0 )
27+
, mIQR( 0 )
28+
{
29+
30+
}
31+
32+
QgsHistogram::~QgsHistogram()
33+
{
34+
35+
}
36+
37+
void QgsHistogram::prepareValues()
38+
{
39+
qSort( mValues.begin(), mValues.end() );
40+
41+
QgsStatisticalSummary s;
42+
s.setStatistics( QgsStatisticalSummary::Max | QgsStatisticalSummary::Min | QgsStatisticalSummary::InterQuartileRange );
43+
s.calculate( mValues );
44+
mMin = s.min();
45+
mMax = s.max();
46+
mIQR = s.interQuartileRange();
47+
}
48+
49+
void QgsHistogram::setValues( const QList<double> &values )
50+
{
51+
mValues = values;
52+
prepareValues();
53+
}
54+
55+
bool QgsHistogram::setValues( QgsVectorLayer *layer, const QString &fieldOrExpression )
56+
{
57+
mValues.clear();
58+
if ( !layer )
59+
return false;
60+
61+
bool ok;
62+
mValues = layer->getDoubleValues( fieldOrExpression, ok );
63+
if ( !ok )
64+
return false;
65+
66+
prepareValues();
67+
return true;
68+
}
69+
70+
double QgsHistogram::optimalBinWidth() const
71+
{
72+
//Freedman-Diaconis rule
73+
return 2.0 * mIQR * qPow( mValues.count(), -1 / 3.0 );
74+
}
75+
76+
int QgsHistogram::optimalNumberBins() const
77+
{
78+
return ceil(( mMax - mMin ) / optimalBinWidth() );
79+
}
80+
81+
QList<double> QgsHistogram::binEdges( int bins ) const
82+
{
83+
double binWidth = ( mMax - mMin ) / bins;
84+
85+
QList<double> edges;
86+
edges << mMin;
87+
double current = mMin;
88+
for ( int i = 0; i < bins; ++i )
89+
{
90+
current += binWidth;
91+
edges << current;
92+
}
93+
return edges;
94+
}
95+
96+
QList<int> QgsHistogram::counts( int bins ) const
97+
{
98+
QList<double> edges = binEdges( bins );
99+
100+
QList<int> binCounts;
101+
binCounts.reserve( bins );
102+
int currentValueIndex = 0;
103+
for ( int i = 0; i < bins; ++i )
104+
{
105+
int count = 0;
106+
while ( mValues.at( currentValueIndex ) < edges.at( i + 1 ) )
107+
{
108+
count++;
109+
currentValueIndex++;
110+
if ( currentValueIndex >= mValues.count() )
111+
break;
112+
}
113+
binCounts << count;
114+
}
115+
116+
if ( currentValueIndex < mValues.count() )
117+
{
118+
//last value needs to be added
119+
binCounts[ bins - 1 ] = binCounts.last() + 1;
120+
}
121+
122+
return binCounts;
123+
}
124+
125+

‎src/core/qgshistogram.h

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/***************************************************************************
2+
qgshistogram.h
3+
--------------
4+
begin : May 2015
5+
copyright : (C) 2015 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSHISTOGRAM_H
19+
#define QGSHISTOGRAM_H
20+
21+
#include <QList>
22+
23+
class QgsVectorLayer;
24+
25+
26+
/** \ingroup core
27+
* \class QgsHistogram
28+
* \brief Calculator for a numeric histogram from a list of values.
29+
*
30+
* \note Added in version 2.9
31+
*/
32+
33+
class CORE_EXPORT QgsHistogram
34+
{
35+
public:
36+
37+
QgsHistogram();
38+
39+
virtual ~QgsHistogram();
40+
41+
/** Assigns numeric source values for the histogram.
42+
* @param values list of doubles
43+
*/
44+
void setValues( const QList<double>& values );
45+
46+
/** Assigns numeric source values for the histogram from a vector layer's field or as the
47+
* result of an expression.
48+
* @param layer vector layer
49+
* @param fieldOrExpression field name or expression to be evaluated
50+
* @returns true if values were successfully set
51+
*/
52+
bool setValues( QgsVectorLayer* layer, const QString& fieldOrExpression );
53+
54+
/** Calculates the optimal bin width using the Freedman-Diaconis rule. Bins widths are
55+
* determined by the inter-quartile range of values and the number of values.
56+
* @returns optimal width for bins
57+
* @see optimalNumberBins
58+
* @note values must first be specified using @link setValues @endlink
59+
*/
60+
double optimalBinWidth() const;
61+
62+
/** Returns the optimal number of bins for the source values, calculated using the
63+
* Freedman-Diaconis rule. The number of bins are determined by the inter-quartile range
64+
* of values and the number of values.
65+
* @returns optimal number of bins
66+
* @see optimalBinWidth
67+
* @note values must first be specified using @link setValues @endlink
68+
*/
69+
int optimalNumberBins() const;
70+
71+
/** Returns a list of edges for the histogram for a specified number of bins. This list
72+
* will be length bins + 1, as both the first and last value are also included.
73+
* @param bins number of bins
74+
* @return list of bin edges
75+
* @note values must first be specified using @link setValues @endlink
76+
*/
77+
QList<double> binEdges( int bins ) const;
78+
79+
/** Returns the calculated list of the counts for the histogram bins.
80+
* @param bins number of histogram bins
81+
* @return list of histogram counts
82+
* @note values must first be specified using @link setValues @endlink
83+
*/
84+
QList<int> counts( int bins ) const;
85+
86+
private:
87+
88+
QList<double> mValues;
89+
double mMax;
90+
double mMin;
91+
double mIQR;
92+
93+
void prepareValues();
94+
95+
};
96+
97+
#endif // QGSHISTOGRAM_H

‎tests/src/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,5 @@ ADD_QGIS_TEST(imageoperationtest testqgsimageoperation.cpp)
156156
ADD_QGIS_TEST(painteffecttest testqgspainteffect.cpp)
157157
ADD_QGIS_TEST(painteffectregistrytest testqgspainteffectregistry.cpp)
158158
ADD_QGIS_TEST(statisticalsummarytest testqgsstatisticalsummary.cpp)
159+
ADD_QGIS_TEST(histogramtest testqgshistogram.cpp)
159160

‎tests/src/core/testqgshistogram.cpp

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/***************************************************************************
2+
testqgshistogram.cpp
3+
--------------------
4+
Date : May 2015
5+
Copyright : (C) 2015 by Nyall Dawson
6+
Email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include <QDir>
17+
#include <QtTest/QtTest>
18+
19+
#include "qgsapplication.h"
20+
#include "qgsvectorlayer.h"
21+
#include "qgsvectordataprovider.h"
22+
#include "qgshistogram.h"
23+
24+
/** \ingroup UnitTests
25+
* This is a unit test for QgsHistogram
26+
*/
27+
class TestQgsHistogram : public QObject
28+
{
29+
Q_OBJECT
30+
31+
public:
32+
TestQgsHistogram();
33+
34+
private slots:
35+
void initTestCase();
36+
void cleanupTestCase();
37+
void init() {}
38+
void cleanup() {}
39+
void optimalBinWidth();
40+
void optimalBinCount();
41+
void binEdges();
42+
void counts();
43+
void fromLayer();
44+
45+
private:
46+
47+
};
48+
49+
TestQgsHistogram::TestQgsHistogram()
50+
{
51+
52+
}
53+
54+
void TestQgsHistogram::initTestCase()
55+
{
56+
QgsApplication::init();
57+
QgsApplication::initQgis();
58+
59+
}
60+
61+
void TestQgsHistogram::cleanupTestCase()
62+
{
63+
QgsApplication::exitQgis();
64+
}
65+
66+
void TestQgsHistogram::optimalBinWidth()
67+
{
68+
QList<double> vals;
69+
vals << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10;
70+
71+
QgsHistogram h;
72+
h.setValues( vals );
73+
QVERIFY( qgsDoubleNear( h.optimalBinWidth(), 4.641, 0.001 ) );
74+
}
75+
76+
void TestQgsHistogram::optimalBinCount()
77+
{
78+
QList<double> vals;
79+
vals << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10;
80+
81+
QgsHistogram h;
82+
h.setValues( vals );
83+
QCOMPARE( h.optimalNumberBins(), 2 );
84+
}
85+
86+
void TestQgsHistogram::binEdges()
87+
{
88+
QList<double> vals;
89+
vals << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10;
90+
91+
QgsHistogram h;
92+
h.setValues( vals );
93+
QList<double> edges = h.binEdges( 3 );
94+
QCOMPARE( edges.count(), 4 );
95+
QCOMPARE( edges.at( 0 ), 1.0 );
96+
QCOMPARE( edges.at( 1 ), 4.0 );
97+
QCOMPARE( edges.at( 2 ), 7.0 );
98+
QCOMPARE( edges.at( 3 ), 10.0 );
99+
}
100+
101+
void TestQgsHistogram::counts()
102+
{
103+
QList<double> vals;
104+
vals << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10;
105+
106+
QgsHistogram h;
107+
h.setValues( vals );
108+
QList<int> counts = h.counts( 1 );
109+
QList<int> expected;
110+
expected << 10;
111+
QCOMPARE( counts, expected );
112+
113+
counts = h.counts( 2 );
114+
expected.clear();
115+
expected << 5 << 5;
116+
QCOMPARE( counts, expected );
117+
118+
counts = h.counts( 5 );
119+
expected.clear();
120+
expected << 2 << 2 << 2 << 2 << 2;
121+
QCOMPARE( counts, expected );
122+
123+
counts = h.counts( 20 );
124+
expected.clear();
125+
expected << 1 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1;
126+
QCOMPARE( counts, expected );
127+
}
128+
129+
void TestQgsHistogram::fromLayer()
130+
{
131+
QgsHistogram h;
132+
133+
QVERIFY( !h.setValues( 0, QString() ));
134+
135+
QgsVectorLayer* layer = new QgsVectorLayer( "Point?field=col1:real", "layer", "memory" );
136+
QVERIFY( layer->isValid() );
137+
QgsFeatureList features;
138+
for ( int i = 1; i <= 10; ++i )
139+
{
140+
QgsFeature f( layer->dataProvider()->fields(), i );
141+
f.setAttribute( "col1", i );
142+
features << f;
143+
}
144+
layer->dataProvider()->addFeatures( features );
145+
146+
QVERIFY( !h.setValues( layer, QString() ));
147+
QVERIFY( h.setValues( layer, QString( "col1" ) ) );
148+
QList<int>counts = h.counts( 5 );
149+
QList<int> expected;
150+
expected << 2 << 2 << 2 << 2 << 2;
151+
QCOMPARE( counts, expected );
152+
153+
delete layer;
154+
}
155+
156+
QTEST_MAIN( TestQgsHistogram )
157+
#include "testqgshistogram.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.