Skip to content

Commit

Permalink
Add quartiles and IQR to QgsStatisticalSummary calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed May 3, 2015
1 parent 154468b commit 31e8611
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 3 deletions.
24 changes: 24 additions & 0 deletions python/core/qgsstatisticalsummary.sip
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class QgsStatisticalSummary
Minority, //!< Minority of values
Majority, //!< Majority of values
Variety, //!< Variety (count of distinct) values
FirstQuartile, //!< First quartile
ThirdQuartile, //!< Third quartile
InterQuartileRange, //!< Inter quartile range (IQR)
All
};

Expand Down Expand Up @@ -127,6 +130,27 @@ class QgsStatisticalSummary
* @see minority
*/
double majority() const;

/** Returns the first quartile of the values. The quartile is calculated using the
* "Tukey's hinges" method.
* @see thirdQuartile
* @see interQuartileRange
*/
double firstQuartile() const;

/** Returns the third quartile of the values. The quartile is calculated using the
* "Tukey's hinges" method.
* @see firstQuartile
* @see interQuartileRange
*/
double thirdQuartile() const;

/** Returns the inter quartile range of the values. The quartiles are calculated using the
* "Tukey's hinges" method.
* @see firstQuartile
* @see thirdQuartile
*/
double interQuartileRange() const;

};

Expand Down
72 changes: 70 additions & 2 deletions src/core/qgsstatisticalsummary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ void QgsStatisticalSummary::reset()
mSampleStdev = 0;
mMinority = 0;
mMajority = 0;
mFirstQuartile = 0;
mThirdQuartile = 0;
mValueCount.clear();
}

Expand Down Expand Up @@ -76,9 +78,13 @@ void QgsStatisticalSummary::calculate( const QList<double> &values )
mSampleStdev = qPow( sumSquared / ( values.count() - 1 ), 0.5 );
}

if ( mStatistics & QgsStatisticalSummary::Median )
QList<double> sorted;
if ( mStatistics & QgsStatisticalSummary::Median
|| mStatistics & QgsStatisticalSummary::FirstQuartile
|| mStatistics & QgsStatisticalSummary::ThirdQuartile
|| mStatistics & QgsStatisticalSummary::InterQuartileRange )
{
QList<double> sorted = values;
sorted = values;
qSort( sorted.begin(), sorted.end() );
bool even = ( mCount % 2 ) < 1;
if ( even )
Expand All @@ -91,6 +97,68 @@ void QgsStatisticalSummary::calculate( const QList<double> &values )
}
}

if ( mStatistics & QgsStatisticalSummary::FirstQuartile
|| mStatistics & QgsStatisticalSummary::InterQuartileRange )
{
if (( mCount % 2 ) < 1 )
{
int halfCount = mCount / 2;
bool even = ( halfCount % 2 ) < 1;
if ( even )
{
mFirstQuartile = ( sorted[halfCount / 2 - 1] + sorted[halfCount / 2] ) / 2.0;
}
else //odd
{
mFirstQuartile = sorted[( halfCount + 1 ) / 2 - 1];
}
}
else
{
int halfCount = mCount / 2 + 1;
bool even = ( halfCount % 2 ) < 1;
if ( even )
{
mFirstQuartile = ( sorted[halfCount / 2 - 1] + sorted[halfCount / 2] ) / 2.0;
}
else //odd
{
mFirstQuartile = sorted[( halfCount + 1 ) / 2 - 1];
}
}
}

if ( mStatistics & QgsStatisticalSummary::ThirdQuartile
|| mStatistics & QgsStatisticalSummary::InterQuartileRange )
{
if (( mCount % 2 ) < 1 )
{
int halfCount = mCount / 2;
bool even = ( halfCount % 2 ) < 1;
if ( even )
{
mThirdQuartile = ( sorted[ halfCount + halfCount / 2 - 1] + sorted[ halfCount + halfCount / 2] ) / 2.0;
}
else //odd
{
mThirdQuartile = sorted[( halfCount + 1 ) / 2 - 1 + halfCount ];
}
}
else
{
int halfCount = mCount / 2 + 1;
bool even = ( halfCount % 2 ) < 1;
if ( even )
{
mThirdQuartile = ( sorted[ halfCount + halfCount / 2 - 2 ] + sorted[ halfCount + halfCount / 2 - 1 ] ) / 2.0;
}
else //odd
{
mThirdQuartile = sorted[( halfCount + 1 ) / 2 - 2 + halfCount ];
}
}
}

if ( mStatistics & QgsStatisticalSummary::Minority || mStatistics & QgsStatisticalSummary::Majority )
{
QList<int> valueCounts = mValueCount.values();
Expand Down
28 changes: 27 additions & 1 deletion src/core/qgsstatisticalsummary.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ class CORE_EXPORT QgsStatisticalSummary
Minority = 512, //!< Minority of values
Majority = 1024, //!< Majority of values
Variety = 2048, //!< Variety (count of distinct) values
All = Count | Sum | Mean | Median | StDev | Max | Min | Range | Minority | Majority | Variety
FirstQuartile = 4096, //!< First quartile
ThirdQuartile = 8192, //!< Third quartile
InterQuartileRange = 16384, //!< Inter quartile range (IQR)
All = Count | Sum | Mean | Median | StDev | Max | Min | Range | Minority | Majority | Variety | FirstQuartile | ThirdQuartile | InterQuartileRange
};
Q_DECLARE_FLAGS( Statistics, Statistic )

Expand Down Expand Up @@ -143,6 +146,27 @@ class CORE_EXPORT QgsStatisticalSummary
*/
double majority() const { return mMajority; }

/** Returns the first quartile of the values. The quartile is calculated using the
* "Tukey's hinges" method.
* @see thirdQuartile
* @see interQuartileRange
*/
double firstQuartile() const { return mFirstQuartile; }

/** Returns the third quartile of the values. The quartile is calculated using the
* "Tukey's hinges" method.
* @see firstQuartile
* @see interQuartileRange
*/
double thirdQuartile() const { return mThirdQuartile; }

/** Returns the inter quartile range of the values. The quartiles are calculated using the
* "Tukey's hinges" method.
* @see firstQuartile
* @see thirdQuartile
*/
double interQuartileRange() const { return mThirdQuartile - mFirstQuartile; }

private:

Statistics mStatistics;
Expand All @@ -157,6 +181,8 @@ class CORE_EXPORT QgsStatisticalSummary
double mSampleStdev;
double mMinority;
double mMajority;
double mFirstQuartile;
double mThirdQuartile;
QMap< double, int > mValueCount;
};

Expand Down
33 changes: 33 additions & 0 deletions tests/src/core/testqgsstatisticalsummary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,39 @@ void TestQgsStatisticSummary::stats()
QCOMPARE( s.variety(), 7 );
QCOMPARE( s.minority(), 3.0 );
QCOMPARE( s.majority(), 12.0 );

//test quartiles. lots of possibilities here, involving odd/even/divisible by 4 counts
values.clear();
values << 7 << 15 << 36 << 39 << 40 << 41;
s.calculate( values );
QCOMPARE( s.median(), 37.5 );
QCOMPARE( s.firstQuartile(), 15.0 );
QCOMPARE( s.thirdQuartile(), 40.0 );
QCOMPARE( s.interQuartileRange(), 25.0 );

values.clear();
values << 7 << 15 << 36 << 39 << 40 << 41 << 43 << 49;
s.calculate( values );
QCOMPARE( s.median(), 39.5 );
QCOMPARE( s.firstQuartile(), 25.5 );
QCOMPARE( s.thirdQuartile(), 42.0 );
QCOMPARE( s.interQuartileRange(), 16.5 );

values.clear();
values << 6 << 7 << 15 << 36 << 39 << 40 << 41 << 42 << 43 << 47 << 49;
s.calculate( values );
QCOMPARE( s.median(), 40.0 );
QCOMPARE( s.firstQuartile(), 25.5 );
QCOMPARE( s.thirdQuartile(), 42.5 );
QCOMPARE( s.interQuartileRange(), 17.0 );

values.clear();
values << 6 << 7 << 15 << 36 << 39 << 40 << 41 << 42 << 43 << 47 << 49 << 50 << 58;
s.calculate( values );
QCOMPARE( s.median(), 41.0 );
QCOMPARE( s.firstQuartile(), 36.0 );
QCOMPARE( s.thirdQuartile(), 47.0 );
QCOMPARE( s.interQuartileRange(), 11.0 );
}

QTEST_MAIN( TestQgsStatisticSummary )
Expand Down

0 comments on commit 31e8611

Please sign in to comment.