Calculating Percentiles

J

JMK

Hi All,

I've read at other sites on-line that to calculate Percentiles in Access you
need to write some VBA code or export the data to Excel.
While I know a little bit of how to write VBA code, I do not know how the
results are stored. And by little bit, I mean I know how to change a buttons
name from one thing to another, or show a calendar, or not show a calendar -
thats about it.

What I really want to do, is once the percentiles are calculated, I would
like to export these results to a table for further analysis.

I was wondering if anyone could provide me with the details on how to code
this.

Thanks ahead of time!
 
O

OssieMac

Not sure that I really understand your problem but here are some hints that
might get you started.
If you create a table in Design View then for the field to hold the
percentage.
Set the Data Type to Number

In the Matrix at the bottom
Set the Field size to Decimal
Set Format to Percentage
Can leave Precision at default of 18
Set Scale to maximum number of decimal places to store.
Set Decimal to the number of decimal places to display.

Now all percentages are actually decimals. Therefore enter the data as
decimals and they will display as percentages.
Examples.
0.12 = 12%
0.1 = 10%
1.1 = 110%

All mathematically calculations use the decimal number and it is the decimal
number that is actually stored. Only the display is a percentage.

Feel free to get back to me if this does not answer your question.
 
O

OssieMac

Postscript to previous post. Access 2007 allows the percentages to be entered
as the actual percentage number or as decimals. That is you can enter 12 or
..12 and both display as 12%. In Access 2002 entering .12 displays 12% but
entering 12 displays 1200%. Don't know about Access 2003.
 
J

JMK

Hi OssieMac,

Thank you for the quick reply!

Its not the percentage that I need, but the Percentile of a recordset - then
exporting those percentiles to a table. For example a mark of 90% may only
reach the 70th percentile, or the 95th - depending upon all other marks in
the record.

Cheers
 
O

OssieMac

OK so I have looked it up in wikipedia for the formulas. I think that I can
do it and since you have no other responses then I am prepared to give it a
go.

Appears there is no standard method for the percentile formulas so the first
thing is to agree on the formula to use. check out the site
http://en.wikipedia.org/wiki/95th_percentile

I should think that the one used for Excel Percentile function should be OK
and simple to use but let me know.

Next is exactly what you want to output. Do you simply want a range of
results for each percentile extracted from the original data or do you want
to output all the records indicating what percentile they fall into.

Next is how to handle the range of equal results that fall both sides of a
percentile. For example 70th percentile might calculate to record 282 but
records 279 to 289 might be all the same result.

Next is what increments you want to use for calculating the percentile. Will
it be a fixed increment or do you want to be able to enter different
iincrements. Aso are all the increments to be the same or can they vary.
 
G

Graham Mandeno

Hi JMK

I haven't tested it thoroughly, but I think the following code will return
the same result as the builtin function in Excel:

Public Function Quantile( _
rs As Variant, _
FldName As String, _
Q As Double, _
N As Integer, _
Optional Interpolate As Boolean = True _
) As Double
'-----------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' © Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' Q : the degree of the quantile to be ascertained
' N : the number of the quantile to be ascertained
' Interpolate : if False, take closer actual value
' if True then interpolate between values
' (optional, default True)
' For example:
' Q = 75, N = 100 gives upper quartile
' Q = 3, N = 10 gives 3rd decile
' Q = 50, N = 100 gives median
'-----------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
If V < 0 Or V > N Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to N"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
Case vbObject
If TypeOf rs Is DAO.Recordset Then
Set rsLocal = rs
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * V / N + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not Interpolate And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(FldName)
If Interpolate And xRec > 0 Then
.MoveNext
qVal = ((.Fields(FldName) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
MsgBox Err.Description, vbExclamation, "Error calculating Quantile"
Resume ProcEnd
End Function
 
O

OssieMac

Very good Graham and it seems a shame being critical but I think you might
need to substitute Q for V in both the following lines. Probably an after
thought to change the variable but not to worry because now I don't feel so
bad about having done that sort of thing myself.

If V < 0 Or V > N Then

xRec = (.RecordCount - 1) * V / N + 1

I tested your function against the Excel Percentile function with 400 random
numbers and it works well.

Now to the OP. If you want to answer my previous questions then I can put
Graham's function to good use (Unless of course Graham wants to continue with
his good work and finish it for you. If you do Graham then let me know and
I'll bow out.)


--
Regards,

OssieMac


Graham Mandeno said:
Hi JMK

I haven't tested it thoroughly, but I think the following code will return
the same result as the builtin function in Excel:

Public Function Quantile( _
rs As Variant, _
FldName As String, _
Q As Double, _
N As Integer, _
Optional Interpolate As Boolean = True _
) As Double
'-----------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' © Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' Q : the degree of the quantile to be ascertained
' N : the number of the quantile to be ascertained
' Interpolate : if False, take closer actual value
' if True then interpolate between values
' (optional, default True)
' For example:
' Q = 75, N = 100 gives upper quartile
' Q = 3, N = 10 gives 3rd decile
' Q = 50, N = 100 gives median
'-----------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
If V < 0 Or V > N Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to N"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
Case vbObject
If TypeOf rs Is DAO.Recordset Then
Set rsLocal = rs
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * V / N + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not Interpolate And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(FldName)
If Interpolate And xRec > 0 Then
.MoveNext
qVal = ((.Fields(FldName) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
MsgBox Err.Description, vbExclamation, "Error calculating Quantile"
Resume ProcEnd
End Function


--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

JMK said:
Hi All,

I've read at other sites on-line that to calculate Percentiles in Access
you
need to write some VBA code or export the data to Excel.
While I know a little bit of how to write VBA code, I do not know how the
results are stored. And by little bit, I mean I know how to change a
buttons
name from one thing to another, or show a calendar, or not show a
calendar -
thats about it.

What I really want to do, is once the percentiles are calculated, I would
like to export these results to a table for further analysis.

I was wondering if anyone could provide me with the details on how to code
this.

Thanks ahead of time!
 
G

Graham Mandeno

Hi OssieMac

Thanks! And yes, you are quite right. It was after 11pm when I was writing
the code and I changed a variable name at the last minute and didn't even
recompile <blush>

It was actually your link to the Wikipedia article that got me interested.
I found this reference http://en.wikipedia.org/wiki/Quantile and based my
algorithm on that. The Interpolate argument (probably better named
"Weighted") switches between calculating the weighted average between the
two possible samples (the Excel algorithm) and simply taking the closer of
the two.

It would also improve the performance of the function to pass it an argument
to indicate that the recordset is already sorted and filtered for nulls.
Then it could be assigned directly to rsSorted.

Feel free to continue helping JMK with this. I'll monitor the thread
anyway. Looking back at JMK's posts, I wonder if he/she wants to ascertain
which percentile a given value falls in, not ascertain the value at a given
percentile. The approach would be somewhat different.

--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

OssieMac said:
Very good Graham and it seems a shame being critical but I think you might
need to substitute Q for V in both the following lines. Probably an after
thought to change the variable but not to worry because now I don't feel
so
bad about having done that sort of thing myself.

If V < 0 Or V > N Then

xRec = (.RecordCount - 1) * V / N + 1

I tested your function against the Excel Percentile function with 400
random
numbers and it works well.

Now to the OP. If you want to answer my previous questions then I can put
Graham's function to good use (Unless of course Graham wants to continue
with
his good work and finish it for you. If you do Graham then let me know and
I'll bow out.)


--
Regards,

OssieMac


Graham Mandeno said:
Hi JMK

I haven't tested it thoroughly, but I think the following code will
return
the same result as the builtin function in Excel:

Public Function Quantile( _
rs As Variant, _
FldName As String, _
Q As Double, _
N As Integer, _
Optional Interpolate As Boolean = True _
) As Double
'-----------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' © Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' Q : the degree of the quantile to be ascertained
' N : the number of the quantile to be ascertained
' Interpolate : if False, take closer actual value
' if True then interpolate between values
' (optional, default True)
' For example:
' Q = 75, N = 100 gives upper quartile
' Q = 3, N = 10 gives 3rd decile
' Q = 50, N = 100 gives median
'-----------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
If V < 0 Or V > N Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to N"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
Case vbObject
If TypeOf rs Is DAO.Recordset Then
Set rsLocal = rs
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * V / N + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not Interpolate And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(FldName)
If Interpolate And xRec > 0 Then
.MoveNext
qVal = ((.Fields(FldName) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
MsgBox Err.Description, vbExclamation, "Error calculating Quantile"
Resume ProcEnd
End Function


--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

JMK said:
Hi All,

I've read at other sites on-line that to calculate Percentiles in
Access
you
need to write some VBA code or export the data to Excel.
While I know a little bit of how to write VBA code, I do not know how
the
results are stored. And by little bit, I mean I know how to change a
buttons
name from one thing to another, or show a calendar, or not show a
calendar -
thats about it.

What I really want to do, is once the percentiles are calculated, I
would
like to export these results to a table for further analysis.

I was wondering if anyone could provide me with the details on how to
code
this.

Thanks ahead of time!
 
J

JMK

G'Day OssieMac and Graham,

Again, thank you for the responses.

To answer both your questions about the type of percentile I need to know
what percentile the value falls into, so the person achieved a mark say of
80% on an exam, and when compared to all others in the class they are in the
79th percentile (if that makes any sense).

I'll be giving Grahams formula a go today, so will let you know how things
go (of course substituting those values where OssieMac pointed out).

Thanks again!

Graham Mandeno said:
Hi OssieMac

Thanks! And yes, you are quite right. It was after 11pm when I was writing
the code and I changed a variable name at the last minute and didn't even
recompile <blush>

It was actually your link to the Wikipedia article that got me interested.
I found this reference http://en.wikipedia.org/wiki/Quantile and based my
algorithm on that. The Interpolate argument (probably better named
"Weighted") switches between calculating the weighted average between the
two possible samples (the Excel algorithm) and simply taking the closer of
the two.

It would also improve the performance of the function to pass it an argument
to indicate that the recordset is already sorted and filtered for nulls.
Then it could be assigned directly to rsSorted.

Feel free to continue helping JMK with this. I'll monitor the thread
anyway. Looking back at JMK's posts, I wonder if he/she wants to ascertain
which percentile a given value falls in, not ascertain the value at a given
percentile. The approach would be somewhat different.

--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

OssieMac said:
Very good Graham and it seems a shame being critical but I think you might
need to substitute Q for V in both the following lines. Probably an after
thought to change the variable but not to worry because now I don't feel
so
bad about having done that sort of thing myself.

If V < 0 Or V > N Then

xRec = (.RecordCount - 1) * V / N + 1

I tested your function against the Excel Percentile function with 400
random
numbers and it works well.

Now to the OP. If you want to answer my previous questions then I can put
Graham's function to good use (Unless of course Graham wants to continue
with
his good work and finish it for you. If you do Graham then let me know and
I'll bow out.)


--
Regards,

OssieMac


Graham Mandeno said:
Hi JMK

I haven't tested it thoroughly, but I think the following code will
return
the same result as the builtin function in Excel:

Public Function Quantile( _
rs As Variant, _
FldName As String, _
Q As Double, _
N As Integer, _
Optional Interpolate As Boolean = True _
) As Double
'-----------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' Q : the degree of the quantile to be ascertained
' N : the number of the quantile to be ascertained
' Interpolate : if False, take closer actual value
' if True then interpolate between values
' (optional, default True)
' For example:
' Q = 75, N = 100 gives upper quartile
' Q = 3, N = 10 gives 3rd decile
' Q = 50, N = 100 gives median
'-----------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
If V < 0 Or V > N Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to N"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
Case vbObject
If TypeOf rs Is DAO.Recordset Then
Set rsLocal = rs
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * V / N + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not Interpolate And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(FldName)
If Interpolate And xRec > 0 Then
.MoveNext
qVal = ((.Fields(FldName) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
MsgBox Err.Description, vbExclamation, "Error calculating Quantile"
Resume ProcEnd
End Function


--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

Hi All,

I've read at other sites on-line that to calculate Percentiles in
Access
you
need to write some VBA code or export the data to Excel.
While I know a little bit of how to write VBA code, I do not know how
the
results are stored. And by little bit, I mean I know how to change a
buttons
name from one thing to another, or show a calendar, or not show a
calendar -
thats about it.

What I really want to do, is once the percentiles are calculated, I
would
like to export these results to a table for further analysis.

I was wondering if anyone could provide me with the details on how to
code
this.

Thanks ahead of time!
 
O

OssieMac

Hi JMK,

For some reason I did not get a notification of your reply and delayed
posting this and after a while I thought I would post what I had done so far
only to see that you had replied.

Anyway you indicated that you are trying it alone with Graham’s function but
as I have already done some work on it then thought you might like to have
it. You might get some ideas for your code.

Note that the entire project would be better with a form to enter the
parameters but I can’t post all that for you so it is another project for you
to create a form and remove the input boxes.

Open Access.
Create a new blank database and save.
Select File -> Get External Data -> Import.
Select the database where you have the table with your list of data.
In the Tables tab select ONLY the required table and click OK.

After the table is imported, make a note of the Table Name and the Field
Name for the input data. You will need them later.

Copy all of the code below into a module. (If Option Compare Database and/or
Option Explicit appears at the top of the editor window then don’t over
write them.)

While in the VBA Editor, Select menu Item Tools -> References.
Scroll down to Microsoft DAO 3.6 Object Library.
Check the box (Ensure you check the box not just select the item).
Click OK.
Click on Save to save the module.

You can run the code from within the VBA window.
Click anywhere within the first sub “Enter_Parametersâ€
Select menu Item Run. (There is also a toolbar button for run but I am not
sure if it is there by default or I added it to my toolbar)
(If you get an error on line ‘Dim rs as DAO.Recordset’ then it is because
the reference Microsoft DAO 3.6 Object Library has not been added as above.)

You will be requested to enter the Input Table name, the Input Table Field
Name, whether you wish to Interpolate and the Starting Percentile. Whatever
number is entered as the starting percentile will increment by these values
up to 100. (I have defaulted the code to Percentile because that was your
original question although the function handles other Quantiles.)

Return to Database window and view the output in table “QuantileOutputâ€

Copy all the following code into a module.

Sub Enter_Parameters()
Dim i As Double
Dim sqlStmt As String
Dim rs As DAO.Recordset
Dim strTblInput As String
Dim strFldInput As String
Dim lngSteps As String
Dim strInterpolate As String
Dim bolInterpolate As Boolean
Dim lngValue As Long

Do While TblExist(strTblInput) = False
strTblInput = InputBox _
("Enter name for the Input table" & _
Chr(13) & "Cancel to exit processing")
If strTblInput = "" Then
MsgBox "No Input or Cancelled by user" _
& Chr(13) & "Processing will terminate"
Exit Sub
End If
'Call function to test that Input table exists
If TblExist(strTblInput) Then
Exit Do
Else
MsgBox "Table does not exist. Re-enter"
End If
Loop

Do
strFldInput = InputBox _
("Enter the name of the field for the Input data" & _
Chr(13) & "Cancel to exit processing")

If strTblInput = "" Then
MsgBox "No Input or Cancelled by user" _
& Chr(13) & "Processing will terminate"
Exit Sub
End If

'Call function to test that field exists
If fldExists(strTblInput, strFldInput) = True Then
Exit Do
Else
MsgBox "Field does not exist. Re-enter"
End If
Loop

Do
lngSteps = InputBox("Enter the starting percentile." _
& Chr(13) & _
"Calculations will step by the entered value")
If lngSteps > 0 And lngSteps <= 100 Then
Exit Do
Else
MsgBox "Must be between 1 and 100. Re-enter"
End If
Loop

Do
strInterpolate = InputBox _
("Enter Y or N to interpolate value" & _
Chr(13) & "Cancel to exit processing")

If strInterpolate = "" Then
MsgBox "No Input or Cancelled by user" _
& Chr(13) & "Processing will terminate"
Exit Sub
End If

Select Case UCase(strInterpolate)
Case "Y", "T"
bolInterpolate = True
Exit Do
Case "N", "F"
bolInterpolate = False
Exit Do
Case Else
MsgBox "Must be Y or N"
End Select
Loop

'Create output table (or delete existing data if exists)
Call Create_Table

'Open the Output table ready to receive data
sqlStmt = "SELECT QuantileOutput.* FROM QuantileOutput;"
Set rs = CurrentDb.OpenRecordset(sqlStmt)

For i = lngSteps To 100 Step lngSteps
lngValue = Quantile(strTblInput, strFldInput, i, _
100, bolInterpolate)

With rs
.AddNew
.Fields("Percentile") = i
.Fields("ReturnedValue") = lngValue
.Update
End With

Next

End Sub
Sub Create_Table()

Dim dbs As Database
Dim tdfNew As TableDef

If TblExist("QuantileOutput") Then
GoTo deleteExistData
End If

Set dbs = CurrentDb

'Create a new TableDef object.
Set tdfNew = dbs.CreateTableDef("QuantileOutput")

With tdfNew
' Create fields and append them to the new TableDef object.
.Fields.Append .CreateField("Percentile", dbLong)
.Fields.Append .CreateField("ReturnedValue", dbLong)
End With
' Append the new TableDef object to the database
dbs.TableDefs.Append tdfNew

Exit Sub

'If table already exists, delete existing data
deleteExistData:
CurrentDb.Execute "Delete * from [QuantileOutput]"

End Sub

Public Function Quantile( _
rs As Variant, _
Fldname As String, _
Q As Double, _
N As Integer, _
Optional Interpolate As Boolean = True) As Double
'-----------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' © Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' Q : the degree of the quantile to be ascertained
' N : the number of the quantile to be ascertained
' Interpolate : if False, take closer actual value
' if True then interpolate between values
' (optional, default True)
' For example:
' Q = 75, N = 100 gives upper quartile
' Q = 3, N = 10 gives 3rd decile
' Q = 50, N = 100 gives median
'-----------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
Dim recNumb As Double
On Error GoTo ProcErr
If Q < 0 Or Q > N Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to N"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
Case vbObject
If TypeOf rs Is DAO.Recordset Then
Set rsLocal = rs
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
rsLocal.Filter = Fldname & " is not Null"
rsLocal.Sort = Fldname
Set rsSorted = rsLocal.OpenRecordset()
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * Q / N + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not Interpolate And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(Fldname)
If Interpolate And xRec > 0 Then
.MoveNext
qVal = ((.Fields(Fldname) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
MsgBox Err.Description, vbExclamation, _
"Error calculating Quantile"
Resume ProcEnd
End Function

Public Function TblExist(TblName As String) As Boolean
Dim tblDef As DAO.TableDef

'Adapted from Stuart McCall's Microsoft Access Pages - Tables

For Each tblDef In CurrentDb.TableDefs
If tblDef.Name = TblName Then
TblExist = True
Exit For
Else
TblExist = False
End If
Next tblDef

End Function

Public Function fldExists(TblName As String, _
Fldname As String) As Boolean
Dim rs As DAO.Recordset
Dim strFld As String

Set rs = CurrentDb.OpenRecordset(TblName)
On Error Resume Next
strFld = rs.Fields(Fldname).Name
fldExists = (Err.Number = 0)
On Error GoTo 0
DoCmd.Close acTable, rs.Name
Set rs = Nothing

End Function


--
Regards,

OssieMac


JMK said:
G'Day OssieMac and Graham,

Again, thank you for the responses.

To answer both your questions about the type of percentile I need to know
what percentile the value falls into, so the person achieved a mark say of
80% on an exam, and when compared to all others in the class they are in the
79th percentile (if that makes any sense).

I'll be giving Grahams formula a go today, so will let you know how things
go (of course substituting those values where OssieMac pointed out).

Thanks again!

Graham Mandeno said:
Hi OssieMac

Thanks! And yes, you are quite right. It was after 11pm when I was writing
the code and I changed a variable name at the last minute and didn't even
recompile <blush>

It was actually your link to the Wikipedia article that got me interested.
I found this reference http://en.wikipedia.org/wiki/Quantile and based my
algorithm on that. The Interpolate argument (probably better named
"Weighted") switches between calculating the weighted average between the
two possible samples (the Excel algorithm) and simply taking the closer of
the two.

It would also improve the performance of the function to pass it an argument
to indicate that the recordset is already sorted and filtered for nulls.
Then it could be assigned directly to rsSorted.

Feel free to continue helping JMK with this. I'll monitor the thread
anyway. Looking back at JMK's posts, I wonder if he/she wants to ascertain
which percentile a given value falls in, not ascertain the value at a given
percentile. The approach would be somewhat different.

--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

OssieMac said:
Very good Graham and it seems a shame being critical but I think you might
need to substitute Q for V in both the following lines. Probably an after
thought to change the variable but not to worry because now I don't feel
so
bad about having done that sort of thing myself.

If V < 0 Or V > N Then

xRec = (.RecordCount - 1) * V / N + 1

I tested your function against the Excel Percentile function with 400
random
numbers and it works well.

Now to the OP. If you want to answer my previous questions then I can put
Graham's function to good use (Unless of course Graham wants to continue
with
his good work and finish it for you. If you do Graham then let me know and
I'll bow out.)


--
Regards,

OssieMac


:

Hi JMK

I haven't tested it thoroughly, but I think the following code will
return
the same result as the builtin function in Excel:

Public Function Quantile( _
rs As Variant, _
FldName As String, _
Q As Double, _
N As Integer, _
Optional Interpolate As Boolean = True _
) As Double
'-----------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' Q : the degree of the quantile to be ascertained
' N : the number of the quantile to be ascertained
' Interpolate : if False, take closer actual value
' if True then interpolate between values
' (optional, default True)
' For example:
' Q = 75, N = 100 gives upper quartile
' Q = 3, N = 10 gives 3rd decile
' Q = 50, N = 100 gives median
'-----------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
If V < 0 Or V > N Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to N"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
Case vbObject
If TypeOf rs Is DAO.Recordset Then
Set rsLocal = rs
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * V / N + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not Interpolate And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(FldName)
If Interpolate And xRec > 0 Then
.MoveNext
qVal = ((.Fields(FldName) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
MsgBox Err.Description, vbExclamation, "Error calculating Quantile"
Resume ProcEnd
End Function


--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

Hi All,

I've read at other sites on-line that to calculate Percentiles in
Access
you
need to write some VBA code or export the data to Excel.
While I know a little bit of how to write VBA code, I do not know how
the
results are stored. And by little bit, I mean I know how to change a
buttons
name from one thing to another, or show a calendar, or not show a
calendar -
thats about it.

What I really want to do, is once the percentiles are calculated, I
would
like to export these results to a table for further analysis.

I was wondering if anyone could provide me with the details on how to
code
this.

Thanks ahead of time!
 
G

Graham Mandeno

Hi JMK

It seems that what you want then is not the percentile, but the percentile
rank (http://en.wikipedia.org/wiki/Percentile_rank)

The following function will calculate this for you. Below it is a rejigged
version of my Quantile function, a little better thought out than the hasty
version I posted the other night <g>

So for a one-off percentile rank you can say:

=QuantileRank( "YourTable", "FieldContainingScore", 80, 100 )

This will return, say, 78.5, meaning that 78.5% of the scores were below 80.

If you want to call it repeatedly on the same recordset, you could open the
recordset first, filtered and sorted, and then pass the recordset variable
and the AlreadySorted flag. Be sure to close the recordset again. For
example:

Dim rsPercentile as DAO.Recordset

Private Sub Report_Open(Cancel as Integer)
Set rsPercentile = CurrentDb.OpenRecordset( _
"Select [FieldContainingScore] from [YourTable] " _
& "where [FieldContainingScore] is not null " _
& "order by [FieldContainingScore]"
End Sub

Private Function GetPercentileRank( Score as Double ) as Double
GetPercentileRank = QuantileRank( _
rsPercentile, "[FieldContainingScore]", Score, 100, True )
End Function

Private Sub Report_Close()
If Not rsPercentile is Nothing Then
rsPercentile.Close
Set rsPercentile = Nothing
End If
End Sub

Then, in a textbox in your report's detail section you could have:
=GetPercentileRank( [FieldContainingScore] )

--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

'============== Start Code =============================
Public Function QuantileRank( _
rs As Variant, _
FldName As String, _
V As Double, _
Optional Q As Integer = 100, _
Optional AlreadySorted As Boolean _
) As Double
'---------------------------------------------------------------------
' Ascertain the quantile rank for a given score from the numeric
' values in a recordset
' See: http://en.wikipedia.org/wiki/Percentile_rank
'
' © Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' V : the score value to be ranked
' Q : the degree of the quantile to be used
' (optional, default=100 [percentile])
' AlreadySorted
' : if True then assume rs is a DAO.Recordset that is
' already sorted and filtered for Nulls
' (optional, default False)
'---------------------------------------------------------------------
Dim N As Long
Dim cFi As Long
Dim Fi As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
AlreadySorted = False ' just in case
Case vbObject
If TypeOf rs Is DAO.Recordset Then
If AlreadySorted Then
Set rsSorted = rs
Else
Set rsLocal = rs
End If
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
If Not AlreadySorted Then
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
End If
With rsSorted
.MoveLast
N = .RecordCount
.FindFirst FldName & ">=" & V
If .NoMatch Then
cFi = N
Else
cFi = .AbsolutePosition
.FindFirst FldName & ">" & V
If .NoMatch Then
Fi = 0
Else
Fi = .AbsolutePosition - cFi
End If
End If
End With
' N is the total number of values in the sample
' cFi is the number of values less than V
' Fi is the number of values equal to V
QuantileRank = (cFi + 0.5 * Fi) / N * Q
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
If Not AlreadySorted Then rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
Dim msg As String
Select Case Err
Case 13
msg = "Invalid field type"
Case 3061, 3265
msg = "Unknown field"
Case Else
msg = Err.Description
End Select
MsgBox "Error calculating Quantile Rank" & vbCrLf & msg, _
vbExclamation, Err.Source
Resume ProcEnd
End Function

Public Function Quantile( _
rs As Variant, _
FldName As String, _
K As Double, _
Optional Q As Integer = 100, _
Optional AlreadySorted As Boolean, _
Optional WeightedAverage As Boolean = True _
) As Double
'---------------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' See: http://en.wikipedia.org/wiki/Quantile
'
' © Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' K : the number of the quantile to be ascertained
' Q : the degree of the quantile to be ascertained
' (optional, default=100 [percentile])
' AlreadySorted
' : if True then assume rs is a DAO.Recordset that is
' already sorted and filtered for Nulls
' (optional, default False)
' WeightedAverage
' : if False, take closer actual value
' if True then use the weighted average of the values
' [this is the method used by Excel]
' (optional, default True)
' For example:
' K = 3, Q = 4 gives upper quartile
' K = 3, Q = 10 gives 3rd decile
' K = 1, Q = 2 gives median
' K = 95, Q = 100 gives 95th percentile
'---------------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
If K < 0 Or K > Q Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to Q"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
AlreadySorted = False ' just in case
Case vbObject
If TypeOf rs Is DAO.Recordset Then
If AlreadySorted Then
Set rsSorted = rs
Else
Set rsLocal = rs
End If
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
If Not AlreadySorted Then
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
End If
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * K / Q + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not WeightedAverage And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(FldName)
If WeightedAverage And xRec > 0 Then
.MoveNext
qVal = ((.Fields(FldName) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
If Not AlreadySorted Then rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
Dim msg As String
Select Case Err
Case 13
msg = "Invalid field type"
Case 3061, 3265
msg = "Unknown field"
Case Else
msg = Err.Description
End Select
MsgBox "Error calculating Quantile" & vbCrLf & msg, _
vbExclamation, Err.Source
Resume ProcEnd
End Function
'============== Start Code =============================

JMK said:
G'Day OssieMac and Graham,

Again, thank you for the responses.

To answer both your questions about the type of percentile I need to know
what percentile the value falls into, so the person achieved a mark say of
80% on an exam, and when compared to all others in the class they are in
the
79th percentile (if that makes any sense).

I'll be giving Grahams formula a go today, so will let you know how things
go (of course substituting those values where OssieMac pointed out).

Thanks again!

Graham Mandeno said:
Hi OssieMac

Thanks! And yes, you are quite right. It was after 11pm when I was
writing
the code and I changed a variable name at the last minute and didn't even
recompile <blush>

It was actually your link to the Wikipedia article that got me
interested.
I found this reference http://en.wikipedia.org/wiki/Quantile and based my
algorithm on that. The Interpolate argument (probably better named
"Weighted") switches between calculating the weighted average between the
two possible samples (the Excel algorithm) and simply taking the closer
of
the two.

It would also improve the performance of the function to pass it an
argument
to indicate that the recordset is already sorted and filtered for nulls.
Then it could be assigned directly to rsSorted.

Feel free to continue helping JMK with this. I'll monitor the thread
anyway. Looking back at JMK's posts, I wonder if he/she wants to
ascertain
which percentile a given value falls in, not ascertain the value at a
given
percentile. The approach would be somewhat different.

--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

OssieMac said:
Very good Graham and it seems a shame being critical but I think you
might
need to substitute Q for V in both the following lines. Probably an
after
thought to change the variable but not to worry because now I don't
feel
so
bad about having done that sort of thing myself.

If V < 0 Or V > N Then

xRec = (.RecordCount - 1) * V / N + 1

I tested your function against the Excel Percentile function with 400
random
numbers and it works well.

Now to the OP. If you want to answer my previous questions then I can
put
Graham's function to good use (Unless of course Graham wants to
continue
with
his good work and finish it for you. If you do Graham then let me know
and
I'll bow out.)


--
Regards,

OssieMac


:

Hi JMK

I haven't tested it thoroughly, but I think the following code will
return
the same result as the builtin function in Excel:

Public Function Quantile( _
rs As Variant, _
FldName As String, _
Q As Double, _
N As Integer, _
Optional Interpolate As Boolean = True _
) As Double
'-----------------------------------------------------------------
' Calculate a given quantile from the numeric values in a recordset
' Graham Mandeno, 2008
' Use freely provided this comment block is preserved.
'
' ARGUMENTS:
' rs : either an already opened DAO.Recordset
' or a string to be passed to db.OpenRecordset
' FldName : the name of a numeric field in the recordset
' Q : the degree of the quantile to be ascertained
' N : the number of the quantile to be ascertained
' Interpolate : if False, take closer actual value
' if True then interpolate between values
' (optional, default True)
' For example:
' Q = 75, N = 100 gives upper quartile
' Q = 3, N = 10 gives 3rd decile
' Q = 50, N = 100 gives median
'-----------------------------------------------------------------
Dim qVal As Double
Dim xRec As Double
Dim iRec As Long
Dim rsLocal As DAO.Recordset
Dim rsSorted As DAO.Recordset
Dim fCloseRS As Boolean
On Error GoTo ProcErr
If V < 0 Or V > N Then
Err.Raise 5, "Quantile", _
"Quantile value must be in the range 0 to N"
End If
Select Case VarType(rs)
Case vbString
Set rsLocal = CurrentDb.OpenRecordset(rs, dbOpenSnapshot)
fCloseRS = True
Case vbObject
If TypeOf rs Is DAO.Recordset Then
Set rsLocal = rs
Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End If
Case Else
Err.Raise 5, "Quantile", "Invalid recordset passed"
End Select
rsLocal.Filter = FldName & " is not Null"
rsLocal.Sort = FldName
Set rsSorted = rsLocal.OpenRecordset()
With rsSorted
.MoveLast
.MoveFirst
xRec = (.RecordCount - 1) * V / N + 1
'xRec now contains the record number we are looking for.
'(may not be a whole number)
iRec = Int(xRec)
xRec = xRec - iRec
If Not Interpolate And xRec >= 0.5 Then iRec = iRec + 1
'iRec now contains first record to look at and
'xRec contains interpolation to next record
.Move iRec - 1
qVal = .Fields(FldName)
If Interpolate And xRec > 0 Then
.MoveNext
qVal = ((.Fields(FldName) - qVal) * xRec) + qVal
End If
End With
Quantile = qVal
ProcEnd:
On Error Resume Next
If Not rsSorted Is Nothing Then
rsSorted.Close
Set rsSorted = Nothing
End If
If Not rsLocal Is Nothing Then
If fCloseRS Then rsLocal.Close
Set rsLocal = Nothing
End If
Exit Function
ProcErr:
MsgBox Err.Description, vbExclamation, "Error calculating Quantile"
Resume ProcEnd
End Function


--
Good Luck :)

Graham Mandeno [Access MVP]
Auckland, New Zealand

Hi All,

I've read at other sites on-line that to calculate Percentiles in
Access
you
need to write some VBA code or export the data to Excel.
While I know a little bit of how to write VBA code, I do not know
how
the
results are stored. And by little bit, I mean I know how to change a
buttons
name from one thing to another, or show a calendar, or not show a
calendar -
thats about it.

What I really want to do, is once the percentiles are calculated, I
would
like to export these results to a table for further analysis.

I was wondering if anyone could provide me with the details on how
to
code
this.

Thanks ahead of time!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top