Auto-concatenate in Excel - excel

I have a huge (~950 variables) survey response spreadsheet, made 2-4x larger than it needs to be because each item's individual response options are reported in separate columns. E.g., If Question 2 is in cell A1 and has 3 response options, these are listed below the question in cells A2-C2, A3-C3, etc. Note: Only one of A2-C2, etc. is filled w/ data for each observation.
It seems clear that I could go through the spreadsheet manually and concatenate A2-C2 using code similar to this:
=CONCATENATE(IF(ISBLANK(A4),"",A4),IF(ISBLANK(B4),"",B4),IF(ISBLANK(C4),"",C4))
But, the above requires manually altering the code (questions have anywhere from 2-6 response options) and copying/pasting it through all observations for each of the 960 variables individually. Because I'd like to finish this before dying of old age, I'd love to have some way of automating the above process.
It would be easy enough to go through and insert an identifier column (w/ no data but w/ some obvious 1st row name) after each variable's last response option so the code knows where to begin looking back for data to concatenate. The code would have to know to stop whenever it hits the previously concatenated result, and begin the current concatenation w/ the subsequent column. Once it hits a certain line (say, 60? I only have ~55 observations), it could just look for the next identifier column and repeat the process.
Any ideas would be much appreciated.
I need to check for blanks so as to not have extra spaces in the data (to aid future analysis).
Hopefully the below will clarify the situation further. You're correct in your earlier comment; each question is followed by 55 rows of observations. For example:
| | Q1 | | || Q2 | ||
|-|--------|---------|--------||---------|--------||
|1| 1 | | || | 2 ||
|2| | 2 | || | 2 ||
|3| | | 3 || | 2 ||
|4| | 2 | || 1 | ||
|5| | | 3 || | ||
|6| 1 | | || | 2 ||
|7| 1 | | || 1 | ||
|8| | | 3 || 1 | ||
|9| | 2 | || | 2 ||
There isn't currently a blank column after the last response option for each question, but (as mentioned in the initial post) I can easily throw one in.

I'm not 100% sure I understand your layout, but you can greatly simplify the concatenation by using & as follows:
=A4&B4&C4
this would have the same effect as your code, and does not require the checks for blank cells.
I'll tackle the rest of the question as information becomes available.
edit: Here's my solution. I'm assuming that the questions are in row 1, and the first set of observations is in row 2, so the last set of observations falls in row 56.
type the number 1 into the cell A58
place the formula: =IF(ISBLANK(B1),A58,A58+1) into cell B58
fill this formula right all the way across all of your questions
in cell A59, enter the formula =MATCH(COLUMN(),58:58,0)
in cell A60, enter the formula =MATCH(COLUMN(),58:58,1)
in cell A61, enter the formula =INDEX(1:1,,A59)
fill all three of these formulas right for 17 cells (assuming you have 17 questions)
in cell A62, enter the formula =SUM(INDIRECT(ADDRESS(ROW(A2),A$59)&":"&ADDRESS(ROW(A2),A$60)))
fill this formula across 17 cells, and down 55 cells.
Here's how it would look for three questions:
| A | B | C | D | E | F | G |
|----|------------------------------------------
| 1 | Qn1 Qn2 Qn3
| 2 | 1 2 1
| 3 | 2 2 2
| .. |
| 58 | 1 1 1 2 2 3 3
| 59 | 1 4 6
| 60 | 3 5 8
| 61 | Qn1 Qn2 Qn3
| 62 | 1 2 1
| 63 | 2 2 2

#EJames: Thanks for the suggestion & pointer to lay out the example graphically. I need to check for blanks so as to not have extra spaces in the data (to aid future analysis).
Hopefully the below will clarify the situation further. You're correct in your earlier comment; each question is followed by 55 rows of observations. For example:
| | Q1 | | || Q2 | ||
|-|--------|---------|--------||---------|--------||
|1| 1 | | || | 2 ||
|2| | 2 | || | 2 ||
|3| | | 3 || | 2 ||
|4| | 2 | || 1 | ||
|5| | | 3 || | ||
|6| 1 | | || | 2 ||
|7| 1 | | || 1 | ||
|8| | | 3 || 1 | ||
|9| | 2 | || | 2 ||
There isn't currently a blank column after the last response option for each question, but (as mentioned in the initial post) I can easily throw one in.
Much obliged.

The fastest way:
Add an new column after column D
Put 'Q1' in cell B1
Put the following formula in: =SUM(B2:D2)
Copy the formula down to the last row
Repeat the steps above for all questions
Select all data (ctrl-*)
Copy the selection
Choose 'Paste Special' from the context menu and choose 'values'
Delete the original columns
If you want you can create a macro that does this automatically.
Here is the macro. It is my no means my best piece of coding ever. What would you expect in 15 minutes. It does the job, although it crashes when finished. ;o)
Open your excel sheet
Make a back up copy
Hit Alt-F11
Insert a new module
Paste the code below
Put the cursor inside the macro
Hit F8 to step through the code
Since you are in Stack Overflow I assume you will be able to adjust the macro to further tailor your needs.
Sub Main()
Dim ColumnsCount As Integer
ColumnsCount = Range("A1").CurrentRegion.Columns.Count
For i = 2 To 20000
Dim CurrentCell As Range
Set CurrentCell = Range("A1").Offset(0, i - 1)
If CurrentCell.Value <> "" Then
CurrentCell.Select
Selection.End(xlToRight).Select
Dim AnswersCount As Integer
AnswersCount = Selection.Column - CurrentCell.Column
CurrentCell.Offset(0, AnswersCount).Select
Selection.EntireColumn.Insert
Selection.Value = CurrentCell.Value
i = i + AnswersCount
Selection.Offset(1, 0).Select
Selection.FormulaR1C1 = "=SUM(RC[" + CStr(AnswersCount * -1) + "]:RC[-1])"
Selection.Copy
Range(Selection, Selection.Offset(100, 0)).Select
ActiveSheet.Paste
Selection.EntireColumn.Select
Application.CutCopyMode = False
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
End If
Next i
End Sub

Thanks for the piece of information. Apart from your way one can also use the & character for concatenation, so instead of: =concatenate(B1,” “,C1,” “,D1) you could use: =B1 & ” ” & C1 & ” ” & D1

Old post but I made this function to concatenate cells. Works similar to SUMIF.
Function CONCIF(rng As Range, criteria As Range, sums As Range)
'Function to concatenate a range of cells if the chosen adjacent cells matches
'the criteria.
'To use:
' Copy and Paste this into a module in VB Editor
' In a cell type =CONCIF(rng, criteria, sums) where:
' rng is the range of cells to match the criteria
' criteria is the value you would to match
' sums is the range of cells to concatenate if criteria matches
' To change what is put between the concatenations, edit the " / " below and put
' whatever you would like in between the quotes.
' Enjoy! -RP
Dim rCell As Range
Dim concat As String
Dim dist As Integer
dist = sums.Column - rng.Column
concat = ""
For Each rCell In rng
If rCell = criteria Then
If concat = "" Then
concat = rCell.Offset(0, dist).Value
Else
concat = concat & " / " & rCell.Offset(0, dist).Value
End If
End If
Next rCell
CONCIF = concat
End Function

Or if you want just a simple way to select a horizontal range and have it concatenate the non-blank cells:
Function CONCIF(rng As Range)
Dim rCell As Range
Dim concat As String
Dim dist As Integer
dist = 0
concat = ""
For Each rCell In rng
If rCell.Value <> "" Then
If concat = "" Then
concat = rCell.Value
Else
concat = concat & ", " & rCell.Value
End If
End If
Next rCell
CONCIF = concat
End Function

Related

VBA macro to subtract one variable number (dynamic) from a fixed number

I am after a dynamic macro that subtracts numbers in C2 onwards from B2 onwards and puts them in D2 onwards.
Numbers in C2 onwards are fixed, although the user will put numbers in B2 onwards. The length of Column C (Hours Charged) changes dependent on the sheet that has been imported. The code is as follows:
Dim O As Long
O = Cells(Rows.Count, "C").End(xlUp).Row
Cells(O + 1, "C").Formula = "=SUM(C2:C" & O & ")"
Cells(O + 1, "C").Interior.Color = RGB(208, 247, 197)
Cells(O + 1, "C").Font.Bold = True
The sheet looks something like this (the first column is B, the last is D):
Hours Budgeted | Hours Charged | Variance
0 | 85 | 0
0 | 31 | 0
0 | 20 | 0
0 | 100 | 0
0 | 75 | 0
To summarise, when a user inputs a value in the Hours Budgeted (B) column, i want it to automatically subtract from the Hours Charged (C) column and display in the Variance (D) column.
The reason why this can't just be a '=C2-B2' is because I have a macro inputting all this data, as well as a separate macro that 'Resets' the sheet (removing all data and formulas).
Thanks all,
Jake.
The following code should do the trick, just add it after the code you posted.
For i = 2 To O
Cells(i, "D").FormulaR1C1 = "=RC[-1]-RC[-2]"
Next i
FormulaR1C1 lets you insert cells references that are relative to the cell you're putting the formula into, so R[2]C[3] would insert a reference to a cell three rows down and three columns to the right.

auto increment rows that belong to the same group of values

in a column, i have some values like this:
|------------|----------------|
| false | abc |
| | def |
| false | mpla |
| | xyz |
|-----------------------------|
and i need to have all rows from each false row, till the next false value,
in a group, which will auto increment:
|------------|----------------|
| 1 | abc |
| 1 | def |
| 2 | mpla |
| 2 | xyz |
|-----------------------------|
which excel formula shoul i try with?
Just something to note, without any helper row: =COUNTIF(INDIRECT("A1:A"&ROW(A1));FALSE). Thought I would put it out there :)
Obviously if your column a contains pure text it becomes: =COUNTIF(INDIRECT("A1:A"&ROW(A1));"FALSE")
Add an extra row before your data (row 1) and then put the formula =C1+IF(A2="false";1;0) in the third (C) column.
As Dmitry noted, you can add an additional row at the top and use it to initialize an accumulator at zero. Each time you run into a non-blank cell, you can increment the previous value by 1.
The reason I didn't check for false, is because false is a bit tricky. If you used TRUE instead of FALSE, your formula for row 2 (onward) would be:
=$C1+IF($A2=TRUE,1,0)
I do not have access to Excel at this time, to write a VB script, but I wrote a simple Google script to populate IDs. I am sure you can do something very similar in VB.
Sample Data
Group Name ID <-- Add third column
FALSE abc
def
FALSE mpla
xyz
You can execute the script to run through the sheet and fill in the ID column.
function autoAassignGroupId() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(),
rangeData = sheet.getDataRange(),
lastColumn = rangeData.getLastColumn(),
lastRow = rangeData.getLastRow(),
searchRange = sheet.getRange(2, 1, lastRow-1, lastColumn-1),
rangeValues = searchRange.getValues(),
auto_index = 0;
for (row = 0 ; row < lastRow - 1; row++) {
if (rangeValues[row][0] === false) auto_index++;
sheet.getRange(row + 2, lastColumn).setValue(auto_index);
}
}

Retrieve the value of the column excel

I need your help, I would like to retrieve the value of the column where the cell contains 1 here is an example:
Image
A | B | C | D
1 | 0 | 0 |
0 | 1 | 1 |
1 | 1 | 0 |
The desired result:
A | B | C | D
1 | 0 | 0 | A
0 | 1 | 1 | B,C
1 | 1 | 0 | A,B
Thank you !
Here is a VBA approach that works for any number of columns:
Function SelectedColumns(R As Range) As String
Dim i As Long, n As Long, count As Long
Dim cols As Variant
n = R.Columns.count
ReDim cols(1 To n)
For i = 1 To n
If R.Cells(1, i).Value = 1 Then
count = count + 1
cols(count) = Split(R.Cells(1, i).Address, "$")(1)
End If
Next i
If count > 0 Then
ReDim Preserve cols(1 To count)
SelectedColumns = Join(cols, ",")
End If
End Function
Put the above code in a standard code module. The, in your example, if you enter =SelectedColumns(A1:C1) in cell D1 and copy down then it will work as expected.
Here is the TEXTJOIN formula that can be used in Office 365 Excel:
=TEXTJOIN(",",,IF(A1:C1=1,{"A","B","C"},""))
This is an array formula and must be confirmed with Ctrl-Shift-Enter instead if Enter When exiting edit mode.
For Earlier versions you can use an if for each and concatenate:
=MID(IF(A1=1,",A","") & IF(B1=1,",B","") & IF(C1=1,",C",""),2,100)
I am a little unclear of your formatting (how you want the answer etc), but perhaps this will help you out a bit.
I am assuming your headers are in cells A1 through C1, then your data in cells A2 through C4. Ane you want the data in Cells D2 to F4.
If you copy the following into cell D2 then use the drag function it will produce the result you are after.
=IF(A2=1,A$1,"")
Have a look at the screen capture where I have displayed the formulas then another with the result displayer.
Formulars Displayed
Just the results
Hope this helps --- Best of Luck !!!

Get a random row in a sheet, where column is a known value

I have an Excel file that contains data like this:
id line | idsector |sector | isSectorPrior |etc...
1 | 1 | east | no
2 | 1 | east | no
3 | 1 | east | yes
4 | 1 | east | yes
5 | 2 | west | yes
6 | 2 | west | yes
7 | 2 | west | no
8 | 2 | west | yes
I need to draw one line where isSectorPrior will be "yes", and then, when I have the row, get id line that is in the cell 1.
So here, it can choose row 3, 4, 5, 6, or 8.
I found a lot or samples, that uses the range (an example), but they can return e.g. row number 7, even though it has a "no". This should not happen.
How can I specify, get a random row, but only where iSectorPrio is yes?
My current code, not so much because I didn't find anything that could match what I want :
'get a sheet to use and find a random row
Set tempSheet = ActiveWorkbook.Sheets(1).Select
'loop 50 times, to draw 50 different lines
For i = 1 To 50
'todo : get randow row, where IsSelectPrio is yes
'then do whatever I want with it, like write copy it in another sheet or a csv
Next i
The difficulty here (for me) is, there is no particular range, all I found were using the range. And considering, thats in the first rows, it it not part of the table.
Is there a way to do what I want?
There are at least two approaches.
Approach 1: Get a random row number (e.g. using the code from that other solution you link to), then check if you have a "yes" on that row. If not, then get a new random number; repeat until you get a yes. (Warning: this will go into an infinite loop if you have no yes'es.)
Approach 2: Start by making a list of row numbers that have a yes; then pick a random one among those.
If your only problem is finding the right range to pick a random row you can do this:
(If your worksheet has to look the same way it looks now, make a copy of it)
Dim ws As Worksheet
Dim sortRangeRows As Integer
Dim lastRow, lastColumn As Integer
'Get sortRange
lastRow = ThisWorkbook.Sheets(1).UsedRange.SpecialCells(xlCellTypeLastCell).Row
lastColumn = ThisWorkbook.Sheets(1).UsedRange.SpecialCells(xlCellTypeLastCell).Column
Set ws = ThisWorkbook.Sheets(1)
Set sortRangeRows = ws.Range(ws.Cells(1, 1), ws1.Cells(lastRow, lastColumn))
'Sort after isSectorPrior:
sortRangeRows.Sort Key1:=sortRangeRows(1, 4), Order1:=xlAscending, Header:=xlYes, Orientation:=xlSortColumns
'Find the last row with a "yes" in it (not entirely sure about this piece of code)
lastRowYes = ws1.Cells.Find("yes", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Now you have your range of rows with a yes.
HTH

Search related range for number between 2 values (vba, Excel)

I'm learning vba and I have an excel table which looks like this:
Range |max | min| discount%
R1 |0 | 9| 0
R2 |10 | 19| 10
R3 |20 | 100| 20
From another excel sheet, I have a field quantity purchased from which I want to retrieve the range coming from this table.
E.g
if qty = 6 then R1, if qty = 56 then R3
and so on
This is normally not so difficult with a vlookup when my table is static. My problem is the fact that the number of ranges can change (due to a macro I wrote). Here it's 3 ranges, but we can have more or less ranges with different values. Can someone please help?
When you're trying to evaluate multiple things you can use "And" in VBA Its just like the excel version, both expressions must return true for you to receive a True answer.
This should get you going in the right direction,
Basic logic
a = b : a equals b
a <= b : a is less than or equal to b
a >= b : a greater than or equal to b
a < b : a is less than b
a > b : a is greater than b
a <> b : a is not equal to b
Sub ranger()
If Range("A1") > 1 And Range("A1") < 3 Then
MsgBox True
Else
MsgBox False
End If
End Sub
Put 2 in cell A1

Resources