Reshaping table Excel PowerQuery - excel

I have a large table in Excel, which is output of a data-gathering tool, that looks more or less like this:
DateA | ValueA | DateB | ValueB | ... | DateZ | ValueZ
---------------------------------------------------------------------------
2019-01-01 | 3 | 2019-01-01 | 6 | ... | 2019-01-04 | 7
2019-01-02 | 1 | 2019-01-04 | 2 | ... | 2019-01-05 | 3
And I'd like to process it so it would like this:
Date | Value | Type
-----------------------------
2019-01-01 | 3 | A
2019-01-02 | 1 | A
2019-01-01 | 6 | B
2019-01-04 | 2 | B
...
2019-01-04 | 7 | Z
2019-01-05 | 3 | Z
Because this is the format, that is used on our sql database.
How to do this in the least tedious way, preferably using PowerQuery? I'd like to avoid brute-force coping and pasting with vba loop.
The number of columns is fixed, but would be nice to have an option to add another one later on, the number of rows would however vary around some value (like 20, 21, 20, 22, 19, 20) day-to-day

Columns are harder to work with, so I'd first transform each column into a new row as a list.
ColumnsToRows =
Table.FromColumns(
{
Table.ToColumns(Source),
Table.ColumnNames(Source)
},
{"ColumnValues","ColumnName"}
)
This should give you a table as follows where each list consists of values in the corresponding column. For example, the top list is {1/1/2019,1/2/2019}. (The from columns part is to add the ColumnName column.)
| ColumnValues | ColumnName |
|--------------|------------|
| [List] | DateA |
| [List] | ValueA |
| [List] | DateB |
| [List] | ValueB |
| [List] | DateZ |
| [List] | ValueZ |
We can then filter this based on the data type in each list. To get the date rows you can write:
DataRows =
Table.SelectRows(
ColumnsToRows,
each Value.Type(List.First([ColumnValues])) = type date
)
Which gets you the following filtered table:
| ColumnValues | ColumnName |
|--------------|------------|
| [List] | DateA |
| [List] | DateB |
| [List] | DateZ |
If you expand the first column with Table.ExpandListColumn(DataRows, "ColumnValues"), then you get
| ColumnValues | ColumnName |
|--------------|------------|
| 1/1/2019 | DateA |
| 1/2/2019 | DateA |
| 1/1/2019 | DateB |
| 1/4/2019 | DateB |
| 1/4/2019 | DateZ |
| 1/5/2019 | DateZ |
The logic is analogous to filter and expand the value rows.
ValueRows =
Table.ExpandListColumn(
Table.SelectRows(
ColumnsToRows,
each Value.Type(List.First([ColumnValues])) = type number
),
"ColumnValues"
)
Which gets you a similar looking table:
| ColumnValues | ColumnName |
|--------------|------------|
| 3 | ValueA |
| 1 | ValueA |
| 6 | ValueB |
| 2 | ValueB |
| 7 | ValueZ |
| 3 | ValueZ |
Now we just need to combine together the columns we want into a single table:
Combine Columns =
Table.FromColumns(
{
DateRows[ColumnValues],
ValueRows[ColumnValues],
ValueRows[ColumnName]
},
{"Date", "Value", "Type"}
)
and then extract the text following Value in the column names.
ExtractType =
Table.TransformColumns(
CombineColumnns,
{{"Type", each Text.AfterDelimiter(_, "Value"), type text}}
)
The final table should be just as specified:
| Date | Value | Type |
|----------|-------|------|
| 1/1/2019 | 3 | A |
| 1/2/2019 | 1 | A |
| 1/1/2019 | 6 | B |
| 1/4/2019 | 2 | B |
| 1/4/2019 | 7 | Z |
| 1/5/2019 | 3 | Z |
All in a single query, the M code looks like this:
let
Source = <Source Goes Here>,
ColumnsToRows = Table.FromColumns({Table.ToColumns(Source), Table.ColumnNames(Source)}, {"ColumnValues","ColumnName"}),
DateRows = Table.ExpandListColumn(Table.SelectRows(ColumnsToRows, each Value.Type(List.First([ColumnValues])) = type date), "ColumnValues"),
ValueRows = Table.ExpandListColumn(Table.SelectRows(ColumnsToRows, each Value.Type(List.First([ColumnValues])) = type number), "ColumnValues"),
CombineColumnns = Table.FromColumns({DateRows[ColumnValues], ValueRows[ColumnValues], ValueRows[ColumnName]},{"Date", "Value", "Type"}),
ExtractType = Table.TransformColumns(CombineColumnns, {{"Type", each Text.AfterDelimiter(_, "Value"), type text}})
in
ExtractType

Related

Filter filter criteria and then apply in countif statement in Excel

I have a table of filter criteria like this:
+----------+----------+------+------+------+
| Category | SpecName | Spec | Pass | Fail |
+----------+----------+------+------+------+
| A | S1 | 3 | | |
| A | S2 | 4 | | |
| B | S1 | 5 | | |
| C | S1 | 2 | | |
+----------+----------+------+------+------+
I have a table I want to apply the filter criteria to like this:
+----------+----+----+
| Category | S1 | S2 |
+----------+----+----+
| A | 5 | 3 |
| B | 4 | |
| A | 5 | 5 |
| C | 2 | |
| A | 2 | 6 |
+----------+----+----+
I want to fill the Pass and Fail columns in the filter criteria table with a count of items in second table with values >= the corresponding spec, like so.
+----------+----------+------+------+------+
| Category | SpecName | Spec | Pass | Fail |
+----------+----------+------+------+------+
| A | S1 | 3 | 1 | 2 |
| A | S2 | 4 | 1 | 2 |
| B | S1 | 5 | 0 | 1 |
| C | S1 | 2 | 1 | 0 |
+----------+----------+------+------+------+
Here are steps for how I might do it in a scripting language:
Filter first table to get all spec filter criteria for the Category on that row, as follows for the first row.
+----------+----------+------+
| Category | SpecName | Spec |
+----------+----------+------+
| A | S1 | 3 |
| A | S2 | 4 |
+----------+----------+------+
Copy table 2 to a variable iTable
+----------+----+----+
| Category | S1 | S2 |
+----------+----+----+
| A | 5 | 3 |
| B | 4 | |
| A | 5 | 5 |
| C | 2 | |
| A | 2 | 6 |
+----------+----+----+
For each spec name:
Find column in iTable with spec name
Filter spec name column in iTable by spec
After all filters applied, we would have:
+----------+----+----+
| Category | S1 | S2 |
+----------+----+----+
| A | 5 | 5 |
+----------+----+----+
Then just count the rows in iTable and assign to the cell in Pass column of the criteria table
Is this possible with Excel formulas?
If not, does anyone know how to do it with VBA?
Looking at an alternative layout for you spec criteria. Expand you columns to suit your need.
With each spec criteria being its own column life gets really easy. You just need to adjust your formula to match the number of criteria you have.
Based on the table at the end for layout, place the following formula in D3 and copy down as required.
=SUMPRODUCT(($G$2:$G$6=A3)*($H$2:$H$6>=B3)*($I$2:$I$6>=C3))
That will give you a count of passing all criteria. Its also a function that performs array like calcs. It could be repeated in the next column but in order to reduce dependency on array calculation and potentially speed things up depending on the amount of data to check, place the following in the top of the fail column and copy down as required:
=COUNTIF($G$2:$G$6,A3)-D3
Basically it subtracts the passes from the total count. This assumes you can only have PASS and FAIL as options.

Excel search range of cells that contain text return entire cell that contains text

I am trying to get a formula to check a set of data for a certain text. For example, assuming the below table starts in Cell A1, I would like to search Columns C,D,E,F,G for a string, and return the entire contents of the cell that contains that string. So for the "AltID 101020", I would like to search columns C-G for the string "Plan" and return the value of "Plan11" in B2, "Plan88" in B5, and "Plan2d" in B7.
A B C D E F G
Data Column1 Column2 Column3 Column4 Column5 Column6
+--------+-------+-------+-------+-------+-------+-------+
1 | AltID |Plans | CovA | CovB |CovC | CovD | CovE |
+--------+-------+-------+-------+-------+-------+-------+
2 | 101020 | | Pol3 |Plan11 | | |Coord2e|
3 | 907030 | | Pol | | Sub5a | Alt24 | |
4 | 805050 | | | | | | |
5 | 778050 | | |Plan88 | Sub7d | |Coord2 |
6 | 232520 | | | | | | |
7 | 357031 | | |Plan2d | Sub7e | | |
8 | ... | ... | ... | ... | ... | ... | ... |
+--------+-------+-------+-------+-------+-------+-------+
Using this formula, you can see if there is the word "plan" in a cell or not. In case not there is an empty string. You can concatenate all those in one cell, and use a MATCH function for using it:
=IF(ISERROR(FIND("plan";D2));"";D2)

Determine range for one value in a column, use to run function over same range in another

Summary
I want to have a column in my spreadsheet that does 2 things.
1) In an ordered column, it will return the range where the column contains a specified value.
2) It will run a function (i.e., =SUM(), =AVERAGE(), etc.) over that same range in a different column.
Examples
Original
| NAME | VAL | FOO |
|-------|-----|-----|
| A | 3 | |
| A | 2 | |
| A | 4 | |
| A | 3 | |
| B | 2 | |
| B | 2 | |
| B | 1 | |
| C | 6 | |
| C | 5 | |
Average
I would want to get the average of VAL for each NAME. I would want the result to be:
| NAME | VAL | FOO |
|-------|-----|-----|
| A | 3 | 3 |
| A | 2 | 3 |
| A | 4 | 3 |
| A | 3 | 3 |
| B | 2 | 1.7 |
| B | 2 | 1.7 |
| B | 1 | 1.7 |
| C | 6 | 5.5 |
| C | 5 | 5.5 |
Sum
Another example would be to get the sum of VAL for each NAME.
| NAME | VAL | FOO |
|-------|-----|-----|
| A | 3 | 12 |
| A | 2 | 12 |
| A | 4 | 12 |
| A | 3 | 12 |
| B | 2 | 5 |
| B | 2 | 5 |
| B | 1 | 5 |
| C | 6 | 11 |
| C | 5 | 11 |
Having "NAME" ordered makes it easy. If "NAME" is in A1. Enter this into C2 for the sum, then fill down:
=IF(A2=A3,C3,SUMIF($A$2:A2,A2,$B$2:B2))
Enter this into C2 for the average, then fill down:
=IF(A2=A3,C3,AVERAGEIF($A$2:A2,A2,$B$2:B2))
Note that the result in C2 won't be what you want until you fill down.
Update for MAXIF
If you don't have Excel 2016, you'll have to use an array formula (commit with ctrl+shift+enter):
=IF(A2=A3,C3,MAX(IF($A$2:A2=A2,$B$2:B2)))

Excel dynamic range in matrix

In my excel worksheet I have a matrix like this:
+---+------------+--------+--------+--------+--------+--------+-------+
| * | A | B | C | D | E | F | Col n |
+---+------------+--------+--------+--------+--------+--------+-------+
| 1 | 01/01/2000 | -1.000 | -1.000 | -1.000 | -1.000 | -1.000 | ... |
| 2 | 01/02/2000 | | 1.200 | 500 | 500 | 500 | ... |
| 3 | 01/03/2001 | | | 1.100 | 800 | 800 | ... |
| 4 | 01/04/2000 | | | | 1.000 | 700 | ... |
| 5 | 01/05/2000 | | | | | 900 | ... |
| 6 | 01/06/2000 | | | | | | ... |
| 7 | 01/07/2000 | | | | | | ... |
+---+------------+--------+--------+--------+--------+--------+-------+
I need a formula for each column (from column 2) with a dynamic range like this:
For Column B:
=XIRR(B1:B1,A1:A1)
For Column C:
=XIRR(C1:C2,A1:A2)
For Column D:
=XIRR(D1:D3,A1:A3)
For Column E:
=XIRR(E1:E4,A1:A4)
and so on.
Is it possible?
Thanks
I think what you are after is:
=XIRR(OFFSET(B$1,0,0,COLUMN()-1),OFFSET($A$1,0,0,COLUMN()-1))
Using OFFSET we can specify the number of rows in our offset range... We can use the COLUMN() number -1 to get 1 for B, 2 for C etc. We start the offset from an unfixed cell for the values (so it moves along the columns) and a fixed one for dates (so it stays in A)
This formula can just be copied along the cells as far as necessary...

Transform values without VBA but with Index and Match

I'm trying to find a solution without macros in excel for following problem:
There is a table containing ratings of a student for different time periods.
So the rating of the student with ID=1 was 1 from January to April and 3 from Mai to June.
Two other students had a constant ranking (6 and 9) from January to June
| A | B | C |D |
---| ----|------------|------------|-------|
1 | ID | START | END |RANKING|
2 | 1 | 01.01.2014 | 30.04.2014 | 1 |
3 | 1 | 01.05.2014 | 30.06.2014 | 3 |
4 | 2 | 01.01.2014 | 30.06.2014 | 6 |
5 | 3 | 01.01.2014 | 30.06.2014 | 9 |
Next table contains IDs (y axis) and Months (x axis)
| F | G | H | I | J | K | L |
---| ----|--------|--------|--------|--------|--------|--------|
1 | ID | 201401 | 201402 | 201403 | 201404 | 201405 | 201406 |
2 | 1 | | | | | | |
3 | 2 | | | | | | |
4 | 3 | | | | | | |
And I wish to feel this second table like this:
| ID | 201401 | 201402 | 201403 | 201404 | 201405 | 201406 |
| ----|--------|--------|--------|--------|--------|--------|
| 1 | 1 | 1 | 1 | 1 | 3 | 3 |
| 2 | 6 | 6 | 6 | 6 | 6 | 6 |
| 3 | 9 | 9 | 9 | 9 | 9 | 9 |
I tried to use Index and Match, but without any good results because I haven't found a posibility to use IF (if (
Could anybody help?
You can get what you're looking for with SUMPRODUCT
Given the layout you provided, this formula should work when put in G2 and filled down and over
=SUMPRODUCT(--($A:$A=$F2),--($B:$B<=G$1),--($C:$C>G$1),$D:$D)
That looks in column A for an ID matching F2, then for every one it finds of those:
It checks the date in column B against the date in G1
It checks the date in column C against the date in G1
If all criteria match, it returns the value in Column D
This assumes you only have one entry for each period, otherwise it will sum them.
Also, you can use SUMIFS, it's a little less easy to read but I think it's slightly more efficient than SUMPRODUCT (I'm not positive, just anecdotal evidence from usage)
=SUMIFS($D:$D,$A:$A,"="&$F3,$B:$B,"<="&G$1,$C:$C,">"&G$1)
It does the exact same thing, just with different syntax.

Resources