How to Find Average of Numbers within a Single Cell - excel

I've been working on this problem for awhile now and I am able to break this out using multiple cells and eventually get the average. But I am unable to construct a single formula to work out the average.
The data will be dynamic, from 2 to more than 8.
Idea is that I can paste the data into a cell and the Average will compute in another cell.
As mentioned, I was able to break it out using many separate cells and also vba, but was thinking of making the spreadsheet clear with a single cell to do this work.
Using this as an example data set:
ABC 106.375/DF 106.99/G 106.5/JK 99.5/
Output: Average = 104.84125
Just trying to retrieve the 3rd number in the data has put my formula into a huge mess and unneeded complication. =MID(G3,LEN(LEFT(G3,FIND("/",G3)-1))+LEN(MID(G3,LEN(LEFT(G3,FIND("/",G3)-1))+2,FIND("/",G3,LEN(LEFT(G3,FIND("/",G3)-1)))-2))+3,FIND("/",G3,LEN(LEFT(G3,FIND("/",G3)-1))+LEN(MID(G3,LEN(LEFT(G3,FIND("/",G3)-1))+2,FIND("/",G3,LEN(LEFT(G3,FIND("/",G3)-1)))-2))+3)-(LEN(LEFT(G3,FIND("/",G3)-1))+LEN(MID(G3,LEN(LEFT(G3,FIND("/",G3)-1))+2,FIND("/",G3,LEN(LEFT(G3,FIND("/",G3)-1)))-2))+2)-1)
I feel so limited that i am unable to keep variables and I am not even at the point when can pull all the numbers together to compute the average.

Here's a regex-based user defined function.
Option Explicit
Function avgNumsOnly(str As String, _
Optional delim As String = ", ")
Dim n As Long, nums() As Variant
Static rgx As Object, cmat As Object
'with rgx as static, it only has to be created once; beneficial when filling a long column with this UDF
If rgx Is Nothing Then
Set rgx = CreateObject("VBScript.RegExp")
End If
avgNumsOnly = vbNullString
With rgx
.Global = True
.MultiLine = False
.Pattern = "\d*\.?\d+"
If .Test(str) Then
Set cmat = .Execute(str)
'resize the nums array to accept the matches
ReDim nums(cmat.Count - 1)
'populate the nums array with the matches
For n = LBound(nums) To UBound(nums)
nums(n) = CDbl(cmat.Item(n))
Next n
'average the nums array
avgNumsOnly = Application.Average(nums)
End If
End With
End Function

Related

Is there built-in excel functions that can check for: "10" (or any number) in "1,3,5-9,13,16-20,23"?

As mentioned in the title, I wonder if there is any way to use built-in functions in excel to see whether a cell contains a specific number and count the total numbers in the cell. The cell can contain a list of numbers seperated by comas, for instance, "1,4,7" or ranges "10-25" or a combination of both. See the print screen.
No, there is not, but you could write a VBA function to do that, something like:
Function NumberInValues(number As String, values As String) As Boolean
Dim n As Integer
n = CInt(number)
Dim parts() As String
parts = Split(values, ",")
For i = LBound(parts) To UBound(parts)
parts(i) = Replace(parts(i), " ", "")
Next
Dim p() As String
Dim first As Integer
Dim last As Integer
Dim tmp As Integer
For i = LBound(parts) To UBound(parts)
p = Split(parts(i), "-")
' If there is only one entry, check for equality:
If UBound(p) - LBound(p) = 0 Then
If n = CInt(p(LBound(p))) Then
NumberInValues = True
Exit Function
End If
Else
' Check against the range of values: assumes the entry is first-last, does not
' check for last > first.
first = CInt(p(LBound(p)))
last = CInt(p(UBound(p)))
If n >= first And n <= last Then
NumberInValues = True
Exit Function
End If
End If
Next
NumberInValues = False
End Function
and then your cell C2 would be
=NumberInValues(B2,A2)
Calculating how many numbers there are in the ranges would be more complicated as numbers and ranges could overlap.
The key part of implementing this is to create a List or Array of individual numbers that includes all the Numbers represented in the first column.
Once that is done, it is trivial to check for an included, or do a count.
This VBA routine returns a list of the numbers
Option Explicit
Function createNumberList(s)
Dim AL As Object
Dim v, w, x, y, I As Long
Set AL = CreateObject("System.Collections.ArrayList")
v = Split(s, ",")
For Each w In v
'If you need to avoid duplicate entries in the array
'uncomment the If Not lines below and remove the terminal double-quote
If IsNumeric(w) Then
'If Not AL.contains(w) Then _"
AL.Add CLng(w)
Else
x = Split(w, "-")
For I = x(0) To x(1)
'If Not AL.contains(I) Then _"
AL.Add I
Next I
End If
Next w
createNumberList = AL.toarray
End Function
IF your numeric ranges might be overlapping, you will need to create a Unique array. You can do that by changing the AL.Add function to first check if the number is contained in the list. In the code above, you can see instructions for that modification.
You can then use this UDF in your table:
C2: =OR($B2=createNumberList($A2))
D2: =COUNT(createNumberList($A2))
Here is a possible formula solution using filterxml as suggested in the comment:
=LET(split,FILTERXML("<s><t>+"&SUBSTITUTE(A2,",","</t><t>+")&"</t></s>","//s/t"),
leftn,LEFT(split,FIND("-",split&"-")-1),
rightn,IFERROR(RIGHT(split,LEN(split)-FIND("-",split)),leftn),
SUM(rightn-leftn+1))
The columns from F onwards show the steps for the string in A2. I had to put plus signs in because Excel converted a substring like "10-15" etc. into a date as usual.
Then to find if a number (in C2 say) is present:
=LET(split,FILTERXML("<s><t>+"&SUBSTITUTE(A2,",","</t><t>+")&"</t></s>","//s/t"),
leftn,LEFT(split,FIND("-",split&"-")-1),
rightn,IFERROR(RIGHT(split,LEN(split)-FIND("-",split)),leftn),
SUM((--leftn<=C2)*(--rightn>=C2))>0)
As noted by #Ron Rosenfeld, it's possible that there may be duplication within the list: the Count formula would be susceptible to double counting in this case, but the Check (to see if a number was in the list) would give the correct result. So the assumptions are:
(1) No duplication (I think it would be fairly straightforward to check for duplication, but less easy to correct it)
(2) No range in wrong order like 15-10 (although this could easily be fixed by putting ABS around the subtraction in the first formula).
Here is a little cheeky piece of code for a VBA solution:
Function pageCount(s As String)
s = Replace(s, ",", ",A")
s = Replace(s, "-", ":A")
s = "A" & s
' s now looks like a list of ranges e.g. "1,2-3" would give "A1,A2:A3"
pageCount = Union(Range(s), Range(s)).Count
End Function
because after all the ranges in the question behave exactly like Excel ranges don't they?
and for inclusion (of a single page)
Function includes(s As String, m As String) As Boolean
Dim isect As Range
s = Replace(s, ",", ",A")
s = Replace(s, "-", ":A")
s = "A" & s
Set isect = Application.Intersect(Range(s), Range("A" & m))
includes = Not (isect Is Nothing)
End Function

Get the total number of group names where there are multiple group names in a single cell surrounded by random text

This is regarding an export of a list from a Sharepoint site where I do not have access to exporting feature, but I can refresh the information in order to gather new information when is added on the Sharepoint site.
The excel list has several columns, out of which one of them is containing names of groups that were created.
There are several thousands of rows, with cells where each cell might contain this kind of data.
I want to be able to only count the number of groups in my excel sheet.
I was initially thinking to find a way to export somehow the data as text and to then try to remove any parts in the string BEFORE the rows (again, all these rows are in the same cell) in order to make the data uniform and to have only the name of the groups and then to try count in a different column the number of items in each cell and to have a sum.
"Please delete following CORP group:
PRD.12.SYOS.EXOA.XW12LAPP0171.TWSUSERS
PRD.12.SYOS.XW12LAPP0171.Administrators
PRD.12.SYOS.EXOA.XW12LAPP0063.TWSAGENT
PRD.12.SYOS.EXOA.XW12LAPP0063.TWSUSERS
PRD.12.SYOS.VM4P.XW12LAPP0063.ADMINISTRATORS
PRD.12.SYOS.VM4P.XW12LAPP0063.RDP
PRD.12.SYOS.XW12LAPP0063.Administrators"
I am a beginner in excel and in coding. I tried using functions in excel like RIGHT, LEFT, MID, LEN but I am still unable to produce anything close to what I need.
I need the total number of the groups in the sheet.
with a helper column you could extract the part after the last dot with
=MID(A1,LOOKUP(9^9,FIND(".",A1,ROW(1:999)))+1,50)
and then count the number of unique group names, which occur with
=SUMPRODUCT((E1:E99<>"")/COUNTIF(E1:E99,E1:E99&""))
Be aware of the fact, that each Administrators and Administrators" get counted as a unique group. You need to strip off unnecessary chars with SUBSTITUTE if you dont want that behaviour.
When your list doesn't have doublures you could use a rather simple formula like:
=LEN(A1)-LEN(SUBSTITUTE(A1,CHR(10),""))-1
It would also be a nice issue to use REGEX on, like so for example:
Function CountGroup(RNG As Range) As Double
Dim regex As Object
Set regex = CreateObject("VBScript.RegExp")
With regex
.Pattern = "(?=.*\d)[^\n " & Chr(34) & "]+"
.Global = True
End With
Set Matches = regex.Execute(RNG.Value)
CountGroup = Matches.Count
End Function
Or when your list can contain doublures:
Function CountGroup(RNG As Range) As Double
Dim regex As Object, ARR1() As String, X As Long, ARR2() As String
Set regex = CreateObject("VBScript.RegExp")
With regex
.Pattern = "(?=.*\d)[^\n " & Chr(34) & "]+"
.Global = True
End With
Set matches = regex.Execute(RNG.Value)
ReDim ARR1(X)
For Each HIT In matches
ARR2 = Filter(SourceArray:=ARR1, Match:=HIT.Value, Include:=True, Compare:=vbTextCompare)
If UBound(ARR2) = -1 Then
ReDim Preserve ARR1(X)
ARR1(X) = HIT.Value
X = X + 1
End If
Next HIT
If IsEmpty(ARR1) Then
CountGroup = 0
Else
CountGroup = UBound(ARR1) - LBound(ARR1)
End If
End Function
Call in sheet through =CountGroup(A1)

Print Up to 300 Strings of Arrays to PDF Based on a Calculated Value

I need to print a string of arrays dependent on a difference of two values on my input page to separate sheets within the same PDF but I have been running into a few issues.
Based on the difference of two cells, the function will determine which arrays to print.
There are two possible solutions I have thought of but have been unsuccessful attempting both.
Indirectly reference a string of arrays in a cell to print such as "abc,bcd,cde,def,efg..."
(As Shown Below) Use conditional if-then functions to invoke the array based on the difference in these two cells
Primary Goals
Print into a single PDF
Determine specific arrays to print depending on the difference in two values contained in a cell on my input page
Allow for PageSetup values (have this figured out)
I am using MSFT 365. I tried initially using an indirect array reference to a cell with a variable value string including the arrays to be included without success.
Next, I tried to hardcode for all 100 possible values for this difference but in that case, I am running into line limits and errors associated with using _ to continue the array function on another line.
If the difference value equals 3, it is shown as below. If the difference value equals 4, you would add another array line including "schedule05","report05","p&l05"
Option Explicit
Sub PrintTest()
'if a certain difference value, use
If (Worksheets("Inputs").Range("D7") - Worksheets("Inputs").Range("D6")) = "3" Then
Dim pageArray As Variant
'set array for given difference
pageArray = Array("schedule01", "report01", "p&l01", _
"schedule02", "report02", "p&l02", _
"schedule03", "report03", "p&l03", _
"schedule04", "report04", "p&l04")
Worksheets("data").Activate
Worksheets("data").PageSetup.CenterHorizontally = True
'page setup values
With ActiveSheet.PageSetup
.FitToPagesWide = 1
.FitToPagesTall = 1
.Orientation = xlLandscape
End With
'call array for print
Worksheets("data").Range("pageArray").PrintOut
Elseif
'Here is where I could put another similar function for a difference of 4
'......
Else
'Here is where I could put another similar function for a difference of x
End If
End Sub
I expected this would get me a PDF where each of these arrays is printed on a separate sheet and will print a selection of arrays based on the difference value.
To expand on my comment, it would look like this:
Dim lDiff As Long
Dim pageArray As Variant
Dim sFormat As String
Dim i As Long, j As Long
'if a certain difference value, use
lDiff = Worksheets("Inputs").Range("D7").Value - Worksheets("Inputs").Range("D6").Value
ReDim pageArray(1 To (lDiff + 1) * 3)
For i = 1 To UBound(pageArray, 1) Step 3
j = j + 1
If j < 100 Then sFormat = "00" Else sFormat = "000"
pageArray(i) = "schedule" & Format(j, sFormat)
pageArray(i + 1) = "report" & Format(j, sFormat)
pageArray(i + 2) = "p&l" & Format(j, sFormat)
MsgBox pageArray(i)
Next i

extract multiple expressions

I have a cell that contains usernames assigned to projects like this
,FC757_random_name,AP372_another_one,FC782_again_different,FC082_samesamebutdifferent,
I need to only extract the alphanumeric values the expressions start with, so everything in between , and _.
I made it work for one expression with the following, but I need all of them.
= MID(A1;FIND(",";A1)+1;FIND("_";A1)-FIND(",";A1)-1)
I also tinkered with Text to Data, but couldn't make it work for multiple lines at once.
Ideally this would work only with formulas, but I guess (/fear) I'll need VBA or Macros, which I have never worked with before.
All help will be appreciated!
Here is a regex based User Defined Function.
Option Explicit
Function extractMultipleExpressions(str As String, _
Optional delim As String = ", ")
Dim n As Long, nums() As Variant
Static rgx As Object, cmat As Object
'with rgx as static, it only has to be created once; beneficial when filling a long column with this UDF
If rgx Is Nothing Then
Set rgx = CreateObject("VBScript.RegExp")
End If
extractMultipleExpressions = vbNullString
With rgx
.Global = True
.MultiLine = False
.Pattern = "[A-Z]{2}[0-9]{3}"
If .Test(str) Then
Set cmat = .Execute(str)
'resize the nums array to accept the matches
ReDim nums(cmat.Count - 1)
'populate the nums array with the matches
For n = LBound(nums) To UBound(nums)
nums(n) = cmat.Item(n)
Next n
'convert the nums array to a delimited string
extractMultipleExpressions = Join(nums, delim)
End If
End With
End Function
I believe you are looking for something like this Press Alt + F11 and then choose Insert > Module and then paste the following code:
Public Function GetUsers(UserNameProject As String)
Dim userArray() As String
Dim users As String
Dim intPos As Integer
'this will split the users into an array based on the commas
userArray = Split(UserNameProject, ",")
'loop through the array and process any non blank element and extract user
'based on the position of the first underscore
For i = LBound(userArray) To UBound(userArray)
If Len(Trim(userArray(i))) > 0 Then
intPos = InStr(1, userArray(i), "_")
users = users & "," & Left(userArray(i), intPos - 1)
End If
Next
GetUsers = users
End Function
If your string is in A1 then use by putting =GetUsers(A1) in the approiate cell. I think this should get you started!
To clean the data of extra commas, use this formula in cell B1:
=TRIM(SUBSTITUTE(SUBSTITUTE(TRIM(SUBSTITUTE(SUBSTITUTE(A1;" ";"||");";";" "));" ";";");"||";" "))
Then use this formula in cell C1 and copy over and down to extract just the part you want from each section:
=IFERROR(INDEX(TRIM(LEFT(SUBSTITUTE(TRIM(MID(SUBSTITUTE($B1;";";REPT(" ";LEN($B1)));LEN($B1)*(ROW($A$1:INDEX($A:$A;LEN($B1)-LEN(SUBSTITUTE($B1;";";""))+1))-1)+1;LEN($B1)));"_";REPT(" ";LEN($B1)));LEN($B1)));COLUMN(A1));"")

Extract maximum number from a string

I am trying to extract all numbers from a string with a function in Excel.
In the second time, I would like to extract the maximum value contains in the string.
My string look likes:
ATCG=12.5,TTA=2.5,TGC=60.28
Desired output: 60.28
In a first time, I am trying to extract all numbers with my function but it stops only on the first figure.
Function MyCode(ByVal txt As String) As String
With CreateObject("VBScript.RegExp")
.Pattern = "\d.+"
If .test(txt) Then MyCode = .Execute(txt)(0)
End With
End Function
Here is some VBA (not vbscript) that you can adapt to you needs:
Public Function MyCode(ByVal txt As String) As String
Dim maxi As Double, db As Double
maxi = -9999
arr = Split(Replace(txt, "=", ","), ",")
For Each a In arr
If IsNumeric(a) Then
db = CDbl(a)
If db > maxi Then maxi = db
End If
Next a
MyCode = CStr(maxi)
End Function
NOTE:
This gives a String and not a Number.
EDIT#1:
In Excel-VBA, the code must be placed in a standard module.
User Defined Functions (UDFs) are very easy to install and use:
ALT-F11 brings up the VBE window
ALT-I
ALT-M opens a fresh module
paste the stuff in and close the VBE window
If you save the workbook, the UDF will be saved with it.
If you are using a version of Excel later then 2003, you must save
the file as .xlsm rather than .xlsx
To remove the UDF:
bring up the VBE window as above
clear the code out
close the VBE window
To use the UDF from Excel:
=MyCode(A1)
To learn more about macros in general, see:
http://www.mvps.org/dmcritchie/excel/getstarted.htm
and
http://msdn.microsoft.com/en-us/library/ee814735(v=office.14).aspx
and for specifics on UDFs, see:
http://www.cpearson.com/excel/WritingFunctionsInVBA.aspx
Macros must be enabled for this to work!
You don't really need VBA for this if you have a version of Excel (2010+) that includes the AGGREGATE function, you can do it with a worksheet formula:
=AGGREGATE(14,6,--TRIM(MID(SUBSTITUTE(SUBSTITUTE(A1,",",REPT(" ",99)),"=",REPT(" ",99)),seq_99,99)),1)
where seq_99 is a Named Formula that refers to:
=IF(ROW(INDEX($1:$65535,1,1):INDEX($1:$65535,255,1))=1,1,(ROW(INDEX($1:$65535,1,1):INDEX($1:$65535,255,1))-1)*99)
The function results in an array, some of the values are numeric; the AGGREGATE function returns the largest value in the array, ignoring errors.
The formulas below are for earlier versions of Excel and must be entered as array formulas, by holding down ctrl + shift while hitting enter If you do this correctly, Excel will place braces {...} around the formula.
If you have 2007, you can use IFERROR
=MAX(IFERROR(--TRIM(MID(SUBSTITUTE(SUBSTITUTE(A2,",",REPT(" ",99)),"=",REPT(" ",99)),seq_99,99)),0))
For earlier versions, you can use:
=MAX(IF(ISERROR(--TRIM(MID(SUBSTITUTE(SUBSTITUTE(A3,",",REPT(" ",99)),"=",REPT(" ",99)),seq_99,99))),0,--TRIM(MID(SUBSTITUTE(SUBSTITUTE(A3,",",REPT(" ",99)),"=",REPT(" ",99)),seq_99,99))))
Your decimal separator may be different from the US decimal separator.
Public Function MyCode(ByVal txt As String) As String
Dim maxi As Double, db As Double
maxi = -9 ^ 9
arr = Split(txt, ",")
For Each a In arr
If InStr(a, "=") Then
a = Mid(a, InStr(a, "=") + 1)
ar = Replace(a, ".", Format(0, "."))
If IsNumeric(ar) Then
db = ar
If db > maxi Then maxi = db: ok = True
End If
End If
Next a
If ok = True Then
MyCode = CStr(maxi)
End If
End Function
Collect all of the mixed numbers as doubles in an array and return the maximum value.
Option Explicit
Option Base 0 '<~~this is the default but I've included it because it has to be 0
Function maxNums(str As String)
Dim n As Long, nums() As Variant
Static rgx As Object, cmat As Object
'with rgx as static, it only has to be created once; beneficial when filling a long column with this UDF
If rgx Is Nothing Then
Set rgx = CreateObject("VBScript.RegExp")
End If
maxNums = vbNullString
With rgx
.Global = True
.MultiLine = False
.Pattern = "\d*\.\d*"
If .Test(str) Then
Set cmat = .Execute(str)
'resize the nums array to accept the matches
ReDim nums(cmat.Count - 1)
'populate the nums array with the matches
For n = LBound(nums) To UBound(nums)
nums(n) = CDbl(cmat.Item(n))
Next n
'test array
'Debug.Print Join(nums, ", ")
'return the maximum value found
maxNums = Application.Max(nums)
End If
End With
End Function

Resources