Dear John,
I analyzed a bunch of pyroxenes. I noticed that when analytical results are displayed (and copied from the log or Analyze windows) with significant figures enabled, the wt% totals as well as calculated mineral formula values do not use the displayed elemental sigfig values....they appear to use the full precision values. So while you see the sigfig-applied elements/oxides, the totals and mineral formulas are not calculated from them.
The mineral formula values (and totals) output by the software thus cannot be directly published along with the sigfig-applied analyses, because the reviewer will find be a discrepancy (in some cases) if they tried to calculate the formula manually from the sigfig values. This obviously makes the formula output by the software less useful.
Is something on the cards in the future to have the sigfigs taken into account in the calculations? Or is there something I'm missing (which is ALWAYS possible)?
Thanks & cheers,
Deon.
Hi Deon,
You are correct.
Propagating the error to the totals is more difficult and I had not taken that extra effort as yet. The same goes for the formula values.
Since the formula values are normalized I think you could just apply the same number of significant digits. What do you think?
I have an example where I am calculating Mg# for a set of analyses to plot zoning profiles. If I calculate the Mg# from the oxide sigfig-applied data I get 83.2 for the 1st analysis. If I use the output formula values I get Mg# 83.7, and applying the sigfigs to the formula values doesn't make it better. In the end the two zoning profiles look different (ignoring uncertainties). Some output values are fine, but some aren't. Likewise, it might be that in some cases your strategy will work fine, but there will probably be cases where it won't.
Thanks,
Deon.
I think the crucial point in your question is:
"(ignoring uncertainties)"
The last significant digit is going to have some uncertainty < 1. So what fraction of the data will show a variance from the average significant digits? It should be a gaussian distribution. Correct?
john
In conclusion, I think the important thing if you don't plan to incorporate the calculations into the software, is to indicate to users via the software manual that the totals and formula values are not publication ready. The intuitive expectation from my perspective as a user is that these specific values are publishable after processing in the software, but this is not the case, as we've discussed. Perhaps I missed a note already in one of the manuals?
Cheers,
Deon.
Hi Deon,
I see where you are going now. Actually the sig fig calculation was originally added as a demonstration for students to show them that just because a computer prints a long line of digits one should not just accept them as is.
For publication in tables, I generally include 3 decimals and the standard deviations along with the detection limits and let the reader see the variance and sensitivity outright. I have never thought the sig fig printout suitable for publishing, though it could be with some additional work.
Actually, if you are interested, here is the code I'm using to calculate these significant figures. I'm sure it could be improved.
Here is the main function:
Function AnalyzeFormatAnalysisResult(row As Integer, chan As Integer, avalue As Single, analysis As TypeAnalysis, sample() As TypeSample) As String
' Format the value based on UseAutomaticFormatForResultsType (0=maximum decimals, 1=significant decimals)
ierror = False
On Error GoTo AnalyzeFormatAnalysisResultError
Dim percenterror As Single, detectionlimit As Single, wtscalratio As Single
' Format for maximum number of decimals
If UseAutomaticFormatForResultsType% = 0 Then
AnalyzeFormatAnalysisResult$ = MiscAutoFormatA$(avalue!)
End If
' Format for significant number of digits, based on counting statistics
If UseAutomaticFormatForResultsType% = 1 And (row% <> 0 And chan% <> 0) Then
percenterror! = ConvertAnalyticalSensitivity2!(row%, chan%, sample())
If ierror Then Exit Function
detectionlimit! = ConvertDetectionLimits2!(row%, chan%, RowUnkZAFCors!(), RowStdAssignsCounts!(), analysis, sample())
If ierror Then Exit Function
wtscalratio! = 1#
If analysis.CalData!(row%, chan%) <> 0# Then
wtscalratio! = analysis.WtsData!(row%, chan%) / analysis.CalData!(row%, chan%) ' for oxide/atomic scaling
If chan% <= sample(1).LastElm% And sample(1).AtomicNums%(chan%) = ATOMIC_NUM_OXYGEN% And sample(1).HydrogenStoichiometryFlag Then wtscalratio! = 1# ' turn off scaling for hydrogen stoichiometry on excess oxygen
detectionlimit! = detectionlimit! / wtscalratio!
End If
AnalyzeFormatAnalysisResult$ = MiscAutoFormatQ$(percenterror!, detectionlimit!, avalue!)
' Save precision and detection limits (to module level arrays) for calculation of total (sum) statistics
tPrecision!(chan%) = Abs(percenterror!)
tDetection!(chan%) = detectionlimit!
' Save maximum precision and detection limits (to module level arrays) for calculation of average statistics
If Abs(percenterror!) < aPrecision!(chan%) Or aPrecision!(chan%) = 0# Then aPrecision!(chan%) = Abs(percenterror!)
If detectionlimit! < aDetection!(chan%) Or aDetection!(chan%) = 0# Then aDetection!(chan%) = detectionlimit!
End If
' Cannot format for significant digits, just do normal format
If UseAutomaticFormatForResultsType% = 1 And (row% = 0 Or chan% = 0) Then
AnalyzeFormatAnalysisResult$ = Format$(Format$(avalue!, f83$), a80$)
End If
Exit Function
' Errors
AnalyzeFormatAnalysisResultError:
MsgBox Error$, vbOKOnly + vbCritical, "AnalyzeFormatAnalysisResult"
ierror = True
Exit Function
End Function
Here are the two functions it calls:
Function ConvertAnalyticalSensitivity2(datarow As Integer, chan As Integer, sample() As TypeSample) As Single
' Calculate percent error for a single element
ierror = False
On Error GoTo ConvertAnalyticalSensitivity2Error
Dim temp1 As Single, temp2 As Single
Dim temp3 As Single, temp4 As Single
ConvertAnalyticalSensitivity2! = 0#
' Init debug variables
bgdtime! = 0#
bgdcount! = 0#
peaktime! = 0#
peakcount! = 0#
' Sanity checks
If sample(1).BgdData(datarow%, chan%) < 0# Then Exit Function
If sample(1).OnTimeData!(datarow%, chan%) <= 0# Then Exit Function
' Check if EDS element
If sample(1).CrystalNames$(chan%) <> EDS_CRYSTAL$ Then
' Determine background count time for unknown
If sample(1).BackgroundTypes%(chan%) <> 1 Then ' 0=off-peak, 1=MAN, 2=multipoint
' 0=linear, 1=average, 2=high only, 3=low only, 4=exponential, 5=slope hi, 6=slope lo, 7=polynomial, 8=multi-point
If sample(1).OffPeakCorrectionTypes%(chan%) = 2 Or sample(1).OffPeakCorrectionTypes%(chan%) = 5 Then
bgdtime! = sample(1).HiTimeData!(datarow%, chan%) ' high only
ElseIf sample(1).OffPeakCorrectionTypes%(chan%) = 3 Or sample(1).OffPeakCorrectionTypes%(chan%) = 6 Then
bgdtime! = sample(1).LoTimeData(datarow%, chan%) ' low only
Else
bgdtime! = sample(1).HiTimeData!(datarow%, chan%) + sample(1).LoTimeData!(datarow%, chan%) ' all other off peak types
End If
Else
bgdtime! = sample(1).OnTimeData!(datarow%, chan%) ' use on-peak time for MAN
End If
Else
bgdtime! = sample(1).OnTimeData!(datarow%, chan%) ' use on-peak time for EDS
End If
If bgdtime! = 0# Then Exit Function
' Determine raw bgd counts for unknown
bgdcount! = sample(1).BgdData!(datarow%, chan%) * bgdtime!
If bgdcount! = 0# Then Exit Function
' Determine peak time and raw peak counts for unknown (use corrected peak intensity + background for greater accuracy)
peaktime! = sample(1).OnTimeData!(datarow%, chan%)
peakcount! = (sample(1).CorData!(datarow%, chan%) + sample(1).BgdData!(datarow%, chan%)) * peaktime!
If peakcount! = 0# Then Exit Function
' De-normalize unknown background counts for beam current
If Not sample(1).CombinedConditionsFlag% Then
Call DataCorrectDataBeamDrift2(bgdcount!, sample(1).OnBeamData(datarow%, chan%)) ' use OnBeamData in case of aggregate intensity calculation (use average aggregate beam)
If ierror Then Exit Function
Else
Call DataCorrectDataBeamDrift2(bgdcount!, sample(1).OnBeamDataArray!(datarow%, chan%)) ' use OnBeamDataArray in case of aggregate intensity calculation (use average aggregate beam)
If ierror Then Exit Function
End If
' De-normalize unknown peak counts for beam current
If Not sample(1).CombinedConditionsFlag% Then
Call DataCorrectDataBeamDrift2(peakcount!, sample(1).OnBeamData!(datarow%, chan%)) ' use OnBeamData in case of aggregate intensity calculation (average aggregate beam)
If ierror Then Exit Function
Else
Call DataCorrectDataBeamDrift2(peakcount!, sample(1).OnBeamDataArray!(datarow%, chan%)) ' use OnBeamDataArray in case of aggregate intensity calculation (average aggregate beam)
If ierror Then Exit Function
End If
' Do not de-normalize for dead time as we only want to utilize *observed* photons!
'If UseDeadtimeCorrectionFlag And sample(1).CrystalNames$(chan%) <> EDS_CRYSTAL$ Then
'Call DataCorrectDataDeadTime2(peakcount!, sample(1).DeadTimes!(chan%))
'If ierror Then Exit Function
'End If
' Check for anomalies
If peaktime! <= 0# Then Exit Function
If bgdtime! <= 0# Then Exit Function
' Calculate analytical sensitivity from Scott and Love 1983
temp1! = peakcount! / peaktime! ^ 2
temp2! = bgdcount! / bgdtime! ^ 2
temp3! = peakcount! / peaktime!
temp4! = bgdcount! / bgdtime!
If temp1! + temp2! < 0# Then Exit Function
If temp3! - temp4 = 0# Then Exit Function
'ConvertAnalyticalSensitivity2! = 100# * Sqr(temp1! + temp2!) / (temp3! - temp4)
ConvertAnalyticalSensitivity2! = Abs(100# * Sqr(temp1! + temp2!) / (temp3! - temp4)) ' assume absolute value
Exit Function
' Errors
ConvertAnalyticalSensitivity2Error:
MsgBox Error$, vbOKOnly + vbCritical, "ConvertAnalyticalSensitivity2"
ierror = True
Exit Function
End Function
Function ConvertDetectionLimits2(datarow As Integer, chan As Integer, tRowUnkCors() As Single, tRowStdCts() As Single, analysis As TypeAnalysis, sample() As TypeSample) As Single
' Calculate detection limit for a single element (off-peak measurement) Scott and Love 1983
ierror = False
On Error GoTo ConvertDetectionLimits2Error
Dim m As Integer
Dim temp As Single, bgdcount2 As Single
Dim tmsg As String
' Default is zero
ConvertDetectionLimits2! = 0#
' Normalize standard counts to pure element counts by apply std k-factor (0 = phi/rho/z, 1,2,3,4 = alpha fits, 5 = calilbration curve, 6 = fundamental parameters)
If CorrectionFlag% = 0 Or CorrectionFlag% = 5 Then
If analysis.StdAssignsKfactors!(chan%) = 0# Then Exit Function
stdcps100! = tRowStdCts!(datarow%, chan%) / analysis.StdAssignsKfactors!(chan%)
' Alpha-Factors (calculate alpha k-fac = conc/beta)
ElseIf CorrectionFlag% > 0 And CorrectionFlag% < 5 Then
If analysis.StdAssignsBetas!(chan%) = 0# Then Exit Function
temp! = (analysis.StdAssignsPercents!(chan%) / 100#) / analysis.StdAssignsBetas!(chan%)
If temp! = 0# Then Exit Function
stdcps100! = tRowStdCts!(datarow%, chan%) / temp! ' leave std counts in cps/nominal beam
' Fundamental parameter corrections
ElseIf CorrectionFlag% = MAXCORRECTION% Then
End If
' Check for valid stdcps100 (pure element intensity)
If stdcps100! = 0# Then Exit Function
' Determine background count time for unknown
If sample(1).CrystalNames$(chan%) <> EDS_CRYSTAL$ Then
' Check if off peak element
If sample(1).BackgroundTypes%(chan%) = 0 Then ' 0=off-peak, 1=MAN, 2=multipoint
' 0=linear, 1=average, 2=high only, 3=low only, 4=exponential, 5=slope hi, 6=slope lo, 7=polynomial, 8=multi-point
If sample(1).OffPeakCorrectionTypes%(chan%) = 2 Or sample(1).OffPeakCorrectionTypes%(chan%) = 5 Then
bgdtime! = sample(1).HiTimeData!(datarow%, chan%) ' high only
ElseIf sample(1).OffPeakCorrectionTypes%(chan%) = 3 Or sample(1).OffPeakCorrectionTypes%(chan%) = 6 Then
bgdtime! = sample(1).LoTimeData(datarow%, chan%) ' low only
Else
bgdtime! = sample(1).HiTimeData!(datarow%, chan%) + sample(1).LoTimeData!(datarow%, chan%) ' all other off peak types
End If
' MAN element
ElseIf sample(1).BackgroundTypes%(chan%) = 1 Then ' 0=off-peak, 1=MAN, 2=multipoint
bgdtime! = sample(1).OnTimeData!(datarow%, chan%) ' use on-peak time for MAN
' MPB element
Else
For m% = 1 To sample(1).MultiPointNumberofPointsAcquireHi%(chan%)
bgdtime! = bgdtime! + sample(1).MultiPointAcquireCountTimesHi!(datarow%, chan%, m%)
Next m%
For m% = 1 To sample(1).MultiPointNumberofPointsAcquireLo%(chan%)
bgdtime! = bgdtime! + sample(1).MultiPointAcquireCountTimesLo!(datarow%, chan%, m%)
Next m%
End If
' EDS element
Else
bgdtime! = sample(1).OnTimeData!(datarow%, chan%) ' use on-peak time for EDS
End If
If bgdtime! = 0# Then Exit Function
' Check for valid count times on background
If bgdtime! <= 0# Then Exit Function
' Load bgd counts in raw cps (one could also use raw peak counts and then divide byu the peak to bgd ratio as in the analytical sensitivity calculation)
bgdcount! = sample(1).BgdData(datarow%, chan%)
' Determine unknown beam current for the element
If Not sample(1).CombinedConditionsFlag Then
bgdbeam! = sample(1).OnBeamData!(datarow%, chan%) ' use OnBeamData in case of aggregate intensity calculation (use average aggregate beam)
Else
bgdbeam! = sample(1).OnBeamDataArray!(datarow%, chan%) ' use OnBeamDataArray in case of aggregate intensity calculation (use average aggregate beam)
End If
' De-normalize unknown background counts for beam current
Call DataCorrectDataBeamDrift2(bgdcount!, bgdbeam!)
If ierror Then Exit Function
' *Do* De-normalize for dead time as we only want to utilize *observed* photons! (do not de-normalize bgd stddev below!)
If UseDeadtimeCorrectionFlag And sample(1).CrystalNames$(chan%) <> EDS_CRYSTAL$ Then
Call DataCorrectDataDeadTime2(bgdcount!, sample(1).DeadTimes!(chan%))
If ierror Then Exit Function
End If
' De-normalize for counting time
bgdcount! = bgdcount! * bgdtime!
' Take square root to get gaussian standard deviation of background
If bgdcount! < 0# Then
If DebugMode Then
tmsg$ = "ConvertDetectionLimits2: negative background counts (" & Format$(sample(1).BgdData(datarow%, chan%)) & ", unable to calculate detection limits on channel " & Format$(chan%) & "..."
Call IOWriteLogRichText(tmsg$, vbNullString, Int(LogWindowFontSize%), vbMagenta, Int(FONT_REGULAR%), Int(0))
End If
Exit Function
End If
' Calculate off-peak variance for this element and row (0=off-peak, 1=MAN, 2=multipoint)
'UseSingerMANExpressionsFlag = False
UseSingerMANExpressionsFlag = True ' utilized in ConvertDetectionLimits3 function
If sample(1).BackgroundTypes%(chan%) <> 1 Then
bgddevraw! = Sqr(bgdcount!) ' off peak or multi-point bgd only variance, assume gaussian statistics
' Calculate MAN (net) variance for this element and row (0=off-peak, 1=MAN, 2=multipoint)
Else
bgddevraw! = ConvertDetectionLimits3!(datarow%, chan%, analysis, sample()) ' MAN net variance, calculate using Jared Singer MAN sensitivity expressions
End If
' Re-normalize background variance for count time
bgddevcps! = bgddevraw! / bgdtime!
' Re-normalize background variance for beam drift
Call DataCorrectDataBeamDrift(bgddevcps!, bgdbeam!)
If ierror Then Exit Function
' Do *not* re-normalize for dead time as we only want to utilize *observed* photons!
'If UseDeadtimeCorrectionFlag And sample(1).CrystalNames$(chan%) <> EDS_CRYSTAL$ Then
'Call DataCorrectDataDeadTime(bgddevcps!, sample(1).DeadTimes!(chan%))
'If ierror Then Exit Function
'End If
' Calculate detection limit based on Scott and Love 1983
If (sample(1).BackgroundTypes%(chan%) = 1 And UseSingerMANExpressionsFlag) Or ConvertDataIsNthPoint(datarow%, chan%, sample()) Or (sample(1).BackgroundTypes%(chan%) <> 1 And NthPointCalculationFlag) Then ' calculate traditional sensitivity
ConvertDetectionLimits2! = 2# * bgddevcps! / stdcps100! * 100# * tRowUnkCors!(datarow%, chan%) ' net intensity statistics- assume 2x net variation for CDL for MAN and Nth point
Else
ConvertDetectionLimits2! = 3# * bgddevcps! / stdcps100! * 100# * tRowUnkCors!(datarow%, chan%) ' bgd intensity only statistics- assume 3x bgd variation for CDL for off-peak
End If
' Save bgd intensity and variance to array for statistics output to log window
If DebugMode Then
' Load off-peak bgd only dev cps for log debug output below
If sample(1).BackgroundTypes%(chan%) <> 1 Then
bgd_onlydevcps! = bgddevcps!
End If
' Re-normalize raw background count to cps and nominal beam again (for ConvertDetectionLimits5 call below)
bgdcount2! = bgdcount! / bgdtime!
Call DataCorrectDataBeamDrift(bgdcount2!, bgdbeam!)
If ierror Then Exit Function
Call ConvertDetectionLimits5(chan%, bgdcount2!, bgd_onlydevcps!, sample())
If ierror Then Exit Function
End If
Exit Function
' Errors
ConvertDetectionLimits2Error:
MsgBox Error$, vbOKOnly + vbCritical, "ConvertDetectionLimits2"
ierror = True
Exit Function
End Function
and these functions are utilized in the output:
Function MiscAutoFormatQ(ByVal precision As Single, ByVal detectionlimit As Single, ByVal treal As Single) As String
' Function to return an automatically formatted real number (based on passed percent precision and detection limit)
ierror = False
On Error GoTo MiscAutoFormatQError
Dim bstring As String
Dim temp As Single
' Check for zero precision or detection limit
If precision! = 0# Or detectionlimit! = 0# Then
MiscAutoFormatQ$ = Format$(Format$(treal!, f83$), a80$)
Exit Function
End If
' Round the float value based on the percent precision
temp! = MiscAutoFormatZ!(precision!, treal!)
If ierror Then Exit Function
bstring$ = Format$(temp!, "General Number")
' Set to "n.d.", if less than detection limit
If treal! < detectionlimit! Then bstring$ = "n.d."
' Format number
MiscAutoFormatQ$ = Format$(bstring$, a80$)
Exit Function
' Errors
MiscAutoFormatQError:
MsgBox Error$, vbOKOnly + vbCritical, "MiscAutoFormatQ"
ierror = True
Exit Function
End Function
Function MiscAutoFormatZ(ByVal precision As Single, ByVal treal As Single) As Single
' Function to return an automatically rounded real number (based on passed percent error)
ierror = False
On Error GoTo MiscAutoFormatZError
Dim nChar As Integer, exponent As Integer
Dim ntemp As Long
Dim mantissa As Single, temp As Single
Dim astring As String
' Check for zero
If treal! = 0# Then Exit Function
' Determine number significant digits to save
nChar% = 1
If Abs(precision!) <= 100# Then nChar% = 1
If Abs(precision!) <= 10# Then nChar% = 2
If Abs(precision!) <= 1# Then nChar% = 3
If Abs(precision!) <= 0.1 Then nChar% = 4
If Abs(precision!) <= 0.01 Then nChar% = 5
If Abs(precision!) <= 0.001 Then nChar% = 6
If Abs(precision!) <= 0.0001 Then nChar% = 7
If treal! >= 100# Then nChar% = nChar% + 1
' Convert number to normalized format between 0 and 1
astring$ = Format$(treal!, e137$) ' note: e137$ = "+.0000000e+00;-.0000000e+00"
mantissa! = Mid$(astring$, 1, 9)
exponent% = Mid$(astring$, 11, 3)
' Calculate as integer based on number of significant digits
temp! = mantissa! * 10 ^ nChar%
' Round to nearest integer and truncate (positive and negative numbers)
temp! = temp! + 0.5
ntemp& = Int(temp!)
temp! = ntemp&
' Calculate back from integer significant digits
temp! = temp! / 10 ^ nChar%
' Apply saved exponent from normalization to recover
temp! = temp! * 10 ^ exponent
' Format number
MiscAutoFormatZ! = Round(temp!, nChar%) ' need to perform an additional rounding here because of rare issues for scientific notation
' If above rounding causes a zero, load without additional rounding
If MiscAutoFormatZ! = 0# Then MiscAutoFormatZ! = temp!
Exit Function
' Errors
MiscAutoFormatZError:
MsgBox Error$, vbOKOnly + vbCritical, "MiscAutoFormatZ"
ierror = True
Exit Function
End Function
Quote from: Owen Neill on July 20, 2015, 10:37:55 AM
Hi John,
When exporting detection limits (i.e. via Output --> Save Specified User Format Output), would I be correct to say that detection limits are always reported in Wt% Elemental, regardless of whether you export the data in Wt% Oxide, Wt% Elemental, Atomic %, etc?
Thanks! (and apologies if I'm posting this in the wrong thread, but I wasn't sure where else to put this)
Hi Owen,
Yes, unless it says otherwise!
In the output file it says: "Si CDL99" which means the concentration detection limit at 99% (3 sigma) and the Si means Si elemental weight percent.
It's the same for the output to the log window:
Detection limit at 99 % Confidence in Elemental Weight Percent (Single Line):
ELEM: Na Si K Al Mg Fe Ca Mn Ni S Cl Ti P F
97 .009 .007 .008 .007 .007 .026 .007 .017 .072 .004 .004 .014 .005 .084
98 .009 .007 .009 .007 .007 .026 .007 .017 .078 .004 .004 .016 .005 .077
99 .009 .007 .008 .007 .007 .026 .007 .017 .074 .004 .004 .015 .005 .078
AVER: .009 .007 .008 .007 .007 .026 .007 .017 .075 .004 .004 .015 .005 .080
SDEV: .000 .000 .000 .000 .000 .000 .000 .000 .003 .000 .000 .001 .000 .004
SERR: .000 .000 .000 .000 .000 .000 .000 .000 .002 .000 .000 .000 .000 .002