Calculating the median of data where some values contain "<" - vba

I need to calculate the median of a set of measurements where in some cases a value was measured, and in some cases the value was below detection (indicated by "<" sign followed by the detection limit, e.g. <1)
Here are some examples of cases that I'm encountering:
2.0; 3.0; <1.0; 4.0 --> median = 2.5
1.0; <0.5; <0.5 --> median = <0.5
1.0; 1.0; <0.5; <0.5 --> median = <0.75
I'm a little stumped about doing this in excel VBA.
How can I do math with the values that have a "<" sign while still keeping track of the "<"?
Any input is much appreciated -- thanks!

Here's something I use:
Public Function DoAvg(rng As Range)
DoAvg = Parse(rng, "Average")
End Function
Public Function DoMedian(rng As Range)
DoMedian = Parse(rng, "Median")
End Function
'This does the work...
Private Function Parse(rng As Range, CalcType As String)
Dim rv, arr() As Single, mods As String, i As Long
Dim c As Range
Dim tmp, m
For Each c In rng.Cells
tmp = Replace(Trim(c.Value), " ", "")
If tmp Like "<*" Or tmp Like ">*" Then
m = Left(tmp, 1)
If Not InStr(mods, m) > 0 Then mods = mods & m
tmp = Right(tmp, Len(tmp) - 1)
End If
If IsNumeric(tmp) And tmp <> "" Then
i = i + 1
ReDim Preserve arr(1 To i)
arr(i) = tmp
End If
Next c
If i > 1 Then
rv = CallByName(Application.WorksheetFunction, CalcType, VbGet, arr)
Parse = IIf(mods <> "", mods, "") & rv
Else
Parse = ""
End if
End Function

Related

How to fix error "Arithmetic operation resulted in an overflow"

Whenever I want to find out a grade in the program I made.
It highlights the statement k = k + 1 and says:
Arithmetic operation resulted in an overflow
Can someone help?
Sub SearchStudentData()
Dim Sname, G As String
Dim Lname, Lgradetext, position, j, k, position1 As Integer
Dim gradefile As IO.StreamReader
Dim Valid As Boolean
Valid = False
Console.WriteLine("Enter the name of the student of whom you want the grade!")
Sname = Console.ReadLine()
Lname = Len(Sname)
gradefile = New IO.StreamReader("D:\Grades.txt")
Do Until gradefile.EndOfStream
gradetext = gradefile.ReadLine()
Lgradetext = Len(gradetext)
j = 0
k = 0
Do
k = k + 1 'It highlights this line of code
position1 = k
Loop Until Mid(gradetext, k, 1) = ":"
Do
j = j + 1
position = j
Loop Until Mid(Lgradetext, j, 1) = ","
If Sname = Right(gradetext, position1 + 1) And Sname = Left(gradetext, position - 1) Then
Valid = True
End If
If Valid = True Then
G = Right(Lgradetext, Lgradetext - 1)
Console.WriteLine(G)
Else
Valid = False
Console.WriteLine("Ypu have failed this PROGRAM")
End If
Loop
gradefile.Close()
End Sub
Your input is not what you expected. The line from your file has no ":" or "," in it, resulting in an infinite loop, and eventually the error when the counter goes past the max. You could use String.IndexOf() to determine if the value is present, instead of a loop. When the value is not present, -1 will be returned. Here's an example:
Dim indexOfColon As Integer = gradetext.IndexOf(":")
Dim indexOfComma As Integer = gradetext.IndexOf(",")
If indexOfColon <> -1 AndAlso indexOfComma <> -1 Then
' ... both ":" and "," were present in the string, do something with those values ...
End If

VBA Split array

I have the following code:
Sub UpdateBlock()
'Define empty variables for each attribute
Dim ent As AcadEntity
Dim oBkRef As AcadBlockReference
Dim Insertpoints As Variant
Dim A As Double
Dim tag As String
Dim material As String
Dim actualLength As String
Dim cutOff As Double
Dim cutLengths As Double
Dim totalLengths As Double
Dim weight As Double
Dim purchaseLength As Double
Dim decimalLength As Double
Dim lengthWeight As Double
Dim totalLengthWeight As Double
Dim cutLengthWeight As Double
Dim cutWeight As Double
Dim order As Double
Dim feet As Double
Dim inches As Double
Dim fraction As Double
Dim fracVal As Variant
'First we go over every object in the modelspace
For Each ent In ThisDrawing.ModelSpace
'Check if the object is a block
If ent.ObjectName = "AcDbBlockReference" Then
Set oBkRef = ent
'If the object is a block then check if its the block we are looking for
If oBkRef.EffectiveName = "AUTOTAG-MATERIAL" Then
A = A + 1
'Get Current Attributes
attlist = oBkRef.GetAttributes
For i = LBound(attlist) To UBound(attlist)
Select Case attlist(i).TagString
Case "ACTUAL-LENGTH"
actualLength = attlist(i).TextString
Case "PURCHASE-LENGTH"
purchaseLength = attlist(i).TextString
Case "CUT-OFF"
cutOff = Frac2Num(attlist(i).TextString)
Case "DECIMAL-LENGTH"
feet = Split(actualLength)(0)
inches = Split(actualLength)(1)
fracVal = Split(actualLength)(2)
If Not IsNull(Split(actualLength)(2)) Then
fraction = Frac2Num(fracVal)
Else
fraction = 0
End If
decimalLength = Round((((feet * 12) + (inches + fraction)) / 12) - cutOff, 2)
attlist(i).TextString = decimalLength
Case "WEIGHT"
weight = attlist(i).TextString
Case "CUT-WEIGHT"
cutWeight = weight * decimalLength
attlist(i).TextString = cutWeight
Case "LENGTH-WEIGHT"
lengthWeight = weight * purchaseLength
attlist(i).TextString = lengthWeight
Case "TOTAL-LENGTHS"
totalLengths = attlist(i).TextString
Case "CUT-LENGTHS"
cutLength = attlist(i).TextString
Case "TOTAL-LENGTH-WEIGHT"
totalLengthWeight = lengthWeight * totalLengths
attlist(i).TextString = totalLengthWeight
Case "CUT-LENGTH-WEIGHT"
totalCutWeight = lengthWeight * cutLength
attlist(i).TextString = totalCutWeight
End Select
Next
End If
End If
Next ent
End Sub
Function Frac2Num(ByVal X As String) As Double
Dim P As Integer, N As Double, Num As Double, Den As Double
X = Trim$(X)
P = InStr(X, "/")
If P = 0 Then
N = Val(X)
Else
Den = Val(Mid$(X, P + 1))
If Den = 0 Then Error 11 ' Divide by zero
X = Trim$(Left$(X, P - 1))
P = InStr(X, " ")
If P = 0 Then
Num = Val(X)
Else
Num = Val(Mid$(X, P + 1))
N = Val(Left$(X, P - 1))
End If
End If
If Den <> 0 Then
N = N + Num / Den
End If
Frac2Num = N
End Function
The variable fraction / fracVal comes from a tag in AutoCAD that is a length, that will always be at least "0 0", but may be "0 0 0" it is a length in feet, inches, and fractional inches. So some possible values could be "8 5", "16 11 11/16", "0 5 3/8" etc.
What I need is a check for when the fraction is not there.
Any suggestions?
I would split the string on the space and see if the ubound of the resulting array is 2. So something like this
If Ubound(Split(thisString, " ")) = 2 then
'fractional part is present
End If
Another option is the Like Operator:
If thisString Like "#* #* #*/#*" Then
# matches any single digit (0–9) and * matches zero or more characters.
but since you split the string anyway, I would store the result of the split in a variable and check the number of items in it with UBound as shown in the other answer.

How can I extract the 'logical_test' from an if statement in excel?

I'm putting together an excel spreadsheet for calculations, and I need to be able to show the formulas to go with the decisions, for the most part its pretty straight forward, but When I come to an 'if' formula in an excel cell, I don't want to show the value_if_true and value_if_false... Just the logical_test value.
Example:
Formula is: =if(and(5<=A1, A1<=10),"Pass", "Fail");
Result will be: "and(5<=A1, A1<=10)"
I need to be able to work with complex logical tests which may include nested if statements, so just splitting at the commas won't work reliably. Similarly the value_if_true and value_if_false statements could also contain if statements.
Any ideas?
If have clear understanding of what you asking for, then you can use something like this (shall be used only with IF() statement :
Function extrIf(ByVal ifstatement As Range) As String
Dim S$, sRev$, x%, k
S = Replace(Replace(ifstatement.Formula, "IF(", "\"), "),", ")|")
sRev = StrReverse(S)
If InStr(1, sRev, "|") > InStr(1, sRev, "\") Or InStr(1, sRev, "|") = 0 Then
x = InStr(1, StrReverse(Left(sRev, InStr(1, sRev, "\"))), ",") - 1
S = Mid(S, 1, Len(S) - InStr(1, sRev, "\") + x) & "|"
End If
sRev = ""
For Each k In Split(S, "|")
If k <> "" Then
If k Like "*\*" Then
sRev = sRev & ", " & Mid(k, InStr(1, k, "\") + 1, 999)
End If
End If
Next
extrIf = Mid(sRev, 3, 999)
End Function
example:
test:
Maybe this is not complete solution for you, but I think it might give you right direction.
If the cell formula starts with an If statement then you can return the logic test (starting after the first open parenthesis) by determining the position of the first comma where the sum of the previous open parenthesis - the sum previous closed = 0.
Formulas
Function ExtractIfTest(Target As Range) As String
Dim ch As String, s As String
Dim openP As Long
Dim x As Long
s = Target.formula
For x = 5 To Len(s)
ch = Mid(s, x, 1)
If Mid(s, x, 1) = "(" Then
openP = openP + 1
ElseIf Mid(s, x, 1) = ")" Then
openP = openP - 1
ElseIf Mid(s, x, 1) = "," And openP = 0 Then
ExtractIfTest = Mid(s, 5, x - 12)
End If
Next
End Function
Results
There might be instances where the is a comma without parenthesis A1,B1. If this happens simple escape them with parenthesis (A1,B1)
I've written an UDF that extract any of the parameters of the target formula. It's close to the one in Thomas answer, but more global and takes into account strings that can enclose commas or parenthesis.
Function ExtractFormulaParameter(Target As Range, Optional Position As Long = 1) As Variant
Dim inString As Boolean
Dim formula As String
Dim st As Long, sp As Long, i As Long, c As String
Dim parenthesis As Long, comma As Long
formula = Target.formula
st = 0: sp = 0
If Position <= 0 Then ExtractFormulaParameter = CVErr(xlErrValue): Exit Function
For i = 1 To Len(formula)
c = Mid$(formula, i, 1)
If inString Then
If c = """" Then
inString = False
End If
Else
Select Case c
Case """"
inString = True
Case "("
parenthesis = parenthesis + 1
If parenthesis = 1 And Position = 1 Then
st = i + 1
End If
Case ")"
parenthesis = parenthesis - 1
If parenthesis = 0 And sp = 0 Then sp = i: Exit For
Case ","
If parenthesis = 1 Then
comma = comma + 1
If Position = 1 And comma = 1 Then sp = i: Exit For
If Position > 1 And comma = Position - 1 Then st = i + 1
If Position > 1 And comma = Position Then sp = i: Exit For
End If
Case Else
End Select
End If
Next i
If st = 0 Or sp = 0 Then
ExtractFormulaParameter = CVErr(xlErrNA)
Else
ExtractFormulaParameter = Mid$(formula, st, sp - st)
End If
End Function
By default it returns the first parameter, but you can also return the second or the third, and it should work with any formula.
Thanks for the replies all. I thought about this more, and ended up coming up with a similar solution to those posted above - essentially string manipulation to extract the text where we expect to find the logical test.
Works well enough, and I'm sure I could use it to extract further logical tests from substrings too.

For-Next Loop in Visual Basic

I want to compute for the binomial coefficient in Visual basic but I can only do the numerator part as I am lost on the denominator part. The equation that I am using is seen in the image below.
Equations
Looking at the C(n,k) equation, I can only do the numerator which is the n factorial. I am having trouble with the denominator. My code for the numerator (n!) is below:
Dim nfactorial As Integer = 1
For i As Integer = 1 To txt3.Text
nfactorial *= i
Next
What should I do about the denominator part? Thanks!
You can't separately calculate numerator and denominator. Have you try finding (30!) in .NET, especially with Integer ?
Is this a homework? Anyway, just take a look at following post: http://csharphelper.com/blog/2014/08/calculate-the-binomial-coefficient-n-choose-k-efficiently-in-c/
Code in vb:
Dim result As Decimal = 1
For i As Integer = 1 To K
result *= N - (K - i)
result /= i
Next
Return result
Try this:
'by example: C(5,3) = 10
Dim n, k As Integer
Dim result As String = ""
n = txt3.Text
For k = 0 To 3
result = result & "For C(" & n & "," & k & ")=" & C(n, k) & vbCrLf
Next
MsgBox(result)
.
.
.
Private Function C(n As Integer, k As Integer)
Dim result As Decimal = 1
For i As Integer = 1 To k
result *= n - (k - i)
result /= i
Next
Return result
End Function
EDIT: For k=0 to 3 and n entered by keyboard.

How to compare Strings for Percentage Match using vb.net?

I am banging my head against the wall for a while now trying different techniques.
None of them are working well.
I have two strings.
I need to compare them and get an exact percentage of match,
ie. "four score and seven years ago" TO "for scor and sevn yeres ago"
Well, I first started by comparing every word to every word, tracking every hit, and percentage = count \ numOfWords. Nope, didn't take into account misspelled words.
("four" <> "for" even though it is close)
Then I started by trying to compare every char in each char, incrementing the string char if not a match (to count for misspellings). But, I would get false hits because the first string could have every char in the second but not in the exact order of the second. ("stuff avail" <> "stu vail" (but it would come back as such, low percentage, but a hit. 9 \ 11 = 81%))
SO, I then tried comparing PAIRS of chars in each string. If string1[i] = string2[k] AND string1[i+1] = string2[k+1], increment the count, and increment the "k" when it doesn't match (to track mispellings. "for" and "four" should come back with a 75% hit.) That doesn't seem to work either. It is getting closer, but even with an exact match it is only returns 94%. And then it really gets screwed up when something is really misspelled. (Code at the bottom)
Any ideas or directions to go?
Code
count = 0
j = 0
k = 0
While j < strTempName.Length - 2 And k < strTempFile.Length - 2
' To ignore non letters or digits '
If Not strTempName(j).IsLetter(strTempName(j)) Then
j += 1
End If
' To ignore non letters or digits '
If Not strTempFile(k).IsLetter(strTempFile(k)) Then
k += 1
End If
' compare pair of chars '
While (strTempName(j) <> strTempFile(k) And _
strTempName(j + 1) <> strTempFile(k + 1) And _
k < strTempFile.Length - 2)
k += 1
End While
count += 1
j += 1
k += 1
End While
perc = count / (strTempName.Length - 1)
Edit: I have been doing some research and I think I initially found the code from here and translated it to vbnet years ago. It uses the Levenshtein string matching algorithm.
Here is the code I use for that, hope it helps:
Sub Main()
Dim string1 As String = "four score and seven years ago"
Dim string2 As String = "for scor and sevn yeres ago"
Dim similarity As Single =
GetSimilarity(string1, string2)
' RESULT : 0.8
End Sub
Public Function GetSimilarity(string1 As String, string2 As String) As Single
Dim dis As Single = ComputeDistance(string1, string2)
Dim maxLen As Single = string1.Length
If maxLen < string2.Length Then
maxLen = string2.Length
End If
If maxLen = 0.0F Then
Return 1.0F
Else
Return 1.0F - dis / maxLen
End If
End Function
Private Function ComputeDistance(s As String, t As String) As Integer
Dim n As Integer = s.Length
Dim m As Integer = t.Length
Dim distance As Integer(,) = New Integer(n, m) {}
' matrix
Dim cost As Integer = 0
If n = 0 Then
Return m
End If
If m = 0 Then
Return n
End If
'init1
Dim i As Integer = 0
While i <= n
distance(i, 0) = System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)
End While
Dim j As Integer = 0
While j <= m
distance(0, j) = System.Math.Max(System.Threading.Interlocked.Increment(j), j - 1)
End While
'find min distance
For i = 1 To n
For j = 1 To m
cost = (If(t.Substring(j - 1, 1) = s.Substring(i - 1, 1), 0, 1))
distance(i, j) = Math.Min(distance(i - 1, j) + 1, Math.Min(distance(i, j - 1) + 1, distance(i - 1, j - 1) + cost))
Next
Next
Return distance(n, m)
End Function
Did not work for me unless one (or both) of following are done:
1) use option compare statement "Option Compare Text" before any Import declarations and before Class definition (i.e. the very, very first line)
2) convert both strings to lowercase using .tolower
Xavier's code must be correct to:
While i <= n
distance(i, 0) = System.Math.Min(System.Threading.Interlocked.Increment(i), i - 1)
End While
Dim j As Integer = 0
While j <= m
distance(0, j) = System.Math.Min(System.Threading.Interlocked.Increment(j), j - 1)
End While