Intersection of Customer product purchase (powerBI)












1














I need help with producing a count of the intersections between customers and which items they have purchased. For example, if there are 5 products, a customer can purchase any single product or any combination of the 5. Customers can also re-purchase a product at any date - this is where my problem arises as an end user wants to be able to see the intersections for any selected date range.



I have managed to come up with a solution which includes the use of parameters but this is not ideal as the end user does not have access to change any parameters of the report.



I'm open to any solution that does not involve parameters, ideally a slicer with dates would be the best solution



The fields I have on the table are customer_ID, date_ID, and product



Example Data



customer_id date_id product
1 9/11/2018 A
1 10/11/2018 A
1 10/11/2018 B
1 11/11/2018 C
1 11/11/2018 A
2 9/11/2018 C
2 10/11/2018 D
2 11/11/2018 E
2 11/11/2018 A
3 10/11/2018 A
3 10/11/2018 B
3 11/11/2018 A
3 11/11/2018 B
3 11/11/2018 B
4 10/11/2018 A
4 11/11/2018 A
5 9/11/2018 A
5 10/11/2018 B
5 10/11/2018 E
5 10/11/2018 D
5 11/11/2018 C
5 11/11/2018 A
6 9/11/2018 A
6 10/11/2018 A
6 11/11/2018 A


Possible output with different slicer selections



output



Any help at all would be greatly appreciated










share|improve this question





























    1














    I need help with producing a count of the intersections between customers and which items they have purchased. For example, if there are 5 products, a customer can purchase any single product or any combination of the 5. Customers can also re-purchase a product at any date - this is where my problem arises as an end user wants to be able to see the intersections for any selected date range.



    I have managed to come up with a solution which includes the use of parameters but this is not ideal as the end user does not have access to change any parameters of the report.



    I'm open to any solution that does not involve parameters, ideally a slicer with dates would be the best solution



    The fields I have on the table are customer_ID, date_ID, and product



    Example Data



    customer_id date_id product
    1 9/11/2018 A
    1 10/11/2018 A
    1 10/11/2018 B
    1 11/11/2018 C
    1 11/11/2018 A
    2 9/11/2018 C
    2 10/11/2018 D
    2 11/11/2018 E
    2 11/11/2018 A
    3 10/11/2018 A
    3 10/11/2018 B
    3 11/11/2018 A
    3 11/11/2018 B
    3 11/11/2018 B
    4 10/11/2018 A
    4 11/11/2018 A
    5 9/11/2018 A
    5 10/11/2018 B
    5 10/11/2018 E
    5 10/11/2018 D
    5 11/11/2018 C
    5 11/11/2018 A
    6 9/11/2018 A
    6 10/11/2018 A
    6 11/11/2018 A


    Possible output with different slicer selections



    output



    Any help at all would be greatly appreciated










    share|improve this question



























      1












      1








      1


      1





      I need help with producing a count of the intersections between customers and which items they have purchased. For example, if there are 5 products, a customer can purchase any single product or any combination of the 5. Customers can also re-purchase a product at any date - this is where my problem arises as an end user wants to be able to see the intersections for any selected date range.



      I have managed to come up with a solution which includes the use of parameters but this is not ideal as the end user does not have access to change any parameters of the report.



      I'm open to any solution that does not involve parameters, ideally a slicer with dates would be the best solution



      The fields I have on the table are customer_ID, date_ID, and product



      Example Data



      customer_id date_id product
      1 9/11/2018 A
      1 10/11/2018 A
      1 10/11/2018 B
      1 11/11/2018 C
      1 11/11/2018 A
      2 9/11/2018 C
      2 10/11/2018 D
      2 11/11/2018 E
      2 11/11/2018 A
      3 10/11/2018 A
      3 10/11/2018 B
      3 11/11/2018 A
      3 11/11/2018 B
      3 11/11/2018 B
      4 10/11/2018 A
      4 11/11/2018 A
      5 9/11/2018 A
      5 10/11/2018 B
      5 10/11/2018 E
      5 10/11/2018 D
      5 11/11/2018 C
      5 11/11/2018 A
      6 9/11/2018 A
      6 10/11/2018 A
      6 11/11/2018 A


      Possible output with different slicer selections



      output



      Any help at all would be greatly appreciated










      share|improve this question















      I need help with producing a count of the intersections between customers and which items they have purchased. For example, if there are 5 products, a customer can purchase any single product or any combination of the 5. Customers can also re-purchase a product at any date - this is where my problem arises as an end user wants to be able to see the intersections for any selected date range.



      I have managed to come up with a solution which includes the use of parameters but this is not ideal as the end user does not have access to change any parameters of the report.



      I'm open to any solution that does not involve parameters, ideally a slicer with dates would be the best solution



      The fields I have on the table are customer_ID, date_ID, and product



      Example Data



      customer_id date_id product
      1 9/11/2018 A
      1 10/11/2018 A
      1 10/11/2018 B
      1 11/11/2018 C
      1 11/11/2018 A
      2 9/11/2018 C
      2 10/11/2018 D
      2 11/11/2018 E
      2 11/11/2018 A
      3 10/11/2018 A
      3 10/11/2018 B
      3 11/11/2018 A
      3 11/11/2018 B
      3 11/11/2018 B
      4 10/11/2018 A
      4 11/11/2018 A
      5 9/11/2018 A
      5 10/11/2018 B
      5 10/11/2018 E
      5 10/11/2018 D
      5 11/11/2018 C
      5 11/11/2018 A
      6 9/11/2018 A
      6 10/11/2018 A
      6 11/11/2018 A


      Possible output with different slicer selections



      output



      Any help at all would be greatly appreciated







      powerbi dax powerbi-desktop






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 13 at 17:59









      Alexis Olson

      12.4k21633




      12.4k21633










      asked Nov 13 at 13:36









      Jonathan Toft

      83




      83
























          3 Answers
          3






          active

          oldest

          votes


















          0














          This is pretty tricky since I can't think of a way to use the values of a dynamically calculated table as a field in a visual. (You can create calculated tables, but those aren't responsive to slicers. You can also create dynamically calculated tables inside of a measure, but measures don't return tables, just single values.)



          The only way I can think of to do this requires creating a table for every possible product combination. However, if you have N products, then this table has 2N rows and that blows up fast.



          Here's a calculated table that will output all the combinations:



          Table2 = 
          VAR N = DISTINCTCOUNT(Table1[product])
          VAR Products = SUMMARIZE(Table1,
          Table1[product],
          "Rank",
          RANKX(ALL(Table1),
          Table1[product],
          MAX(Table1[product]),
          ASC,
          Dense
          )
          )
          VAR Bits = SELECTCOLUMNS(GENERATESERIES(1, N), "Bit", [Value])
          VAR BinaryString =
          ADDCOLUMNS(
          GENERATESERIES(1, 2^N),
          "Binary",
          CONCATENATEX(
          Bits,
          MOD( TRUNC( [Value] / POWER(2, [Bit]-1) ), 2)
          ,,[Bit]
          ,DESC
          )
          )
          RETURN
          ADDCOLUMNS(
          BinaryString,
          "Combination",
          CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), "")
          )


          Then add a calculated column to get the column delimited version:



          Delimited = 
          VAR Length = LEN(Table2[Combination])
          RETURN
          CONCATENATEX(
          GENERATESERIES(1,Length),
          MID(Table2[Combination], [Value], 1),
          ","
          )


          If you put Delimited the Rows section on a matrix visual and the following measure in the Values section:



          customers = 
          VAR Summary = SUMMARIZE(Table1,
          Table1[customer_id],
          "ProductList",
          CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
          RETURN SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))


          And filter out any 0 customer values, you should get something like this:



          Slicer and Matrix





          So yeah... not a great solution, especially when N gets big, but maybe better than nothing?





          Edit:



          In order to work for longer product names, let's use a delimiter in the Combination concatenation:



           CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), ",")


          (Note the "" to "," change at the end.)



          And then rewrite the Delimited calculated column to remove excess commas.



          Delimited = 
          VAR RemoveMultipleCommas =
          SUBSTITUTE(
          SUBSTITUTE(
          SUBSTITUTE(
          SUBSTITUTE(Table2[Combination], ",,", ","),
          ",,", ","),
          ",,", ","),
          ",,", ",")
          VAR LeftComma = (LEFT(Table2[Combination]) = ",")
          VAR RightComma = (RIGHT(Table2[Combination]) = ",")
          RETURN
          IF(RemoveMultipleCommas <> ",",
          MID(RemoveMultipleCommas,
          1 + LeftComma,
          LEN(RemoveMultipleCommas) - RightComma - LeftComma
          ), "")


          Finally, let's modify the customers measure a bit so it can subtotal.



          customers = 
          VAR Summary = SUMMARIZE(Table1,
          Table1[customer_id],
          "ProductList",
          CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
          VAR CustomerCount = SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))
          VAR Total = IF(ISFILTERED(Table2[Delimited]), CustomerCount, COUNTROWS(Summary))
          RETURN IF(Total = 0, BLANK(), Total)


          The Total variable gives the total customer count for the total. Note that I've also set zeros to return as blank so that you don't need to filter out zeros (it will automatically hide those rows).



          Matrix and Slicer






          share|improve this answer























          • That works a treat, thank you! For readability, if I wanted to edit it slightly to have the product names rather than a single letter for each product, would this be easy to do? Also, the total in the matrix shows the MAX value and I can't find a way of editing the measure so that it shows the SUM instead?
            – Jonathan Toft
            Nov 14 at 11:15










          • Please see my edit.
            – Alexis Olson
            Nov 14 at 16:21










          • Thank you so much! I just had to add an order expression into the concantenatex in the measure but it now works perfectly!
            – Jonathan Toft
            Nov 15 at 9:12



















          0














          You can also try this measure to calculate the result. 

          [Count Of Customers] :=
          VAR var_products_selection_count = DISTINCTCOUNT ( Sales[product] )
          VAR var_customers = VALUES ( Sales[customer_id] )
          VAR var_customers_products_count =
          ADDCOLUMNS(
          var_customers,
          "products_count",
          VAR var_products_count =
          COUNTROWS (
          FILTER (
          CALCULATETABLE ( VALUES ( Sales[product] ) ),
          CONTAINS (
          Sales,
          Sales[product],
          Sales[product]
          )
          )
          )
          RETURN var_products_count
          )
          RETURN
          COUNTROWS (
          FILTER (
          var_customers_products_count,
          [products_count] = var_products_selection_count
          )
          )





          share|improve this answer





























            0














            I think I've found a better solution/workaround that doesn't require precomputing all possible combinations. The key is to use a rank/index as a base column and then built off of that.



            Since the customer_id is already nicely indexed starting from 1 with no gaps, in this case, I will use that, but if it weren't, then you'd want to create an index column to use instead. Note that there cannot be more distinct product combinations within a given filter context than there are customers since each customer only has a single combination.



            For each index/rank we want to find the product combination that is associated with it and the number of customers for that combination.



            ProductCombo =
            VAR PerCustomer =
            SUMMARIZE (
            ALLSELECTED ( Table1 ),
            Table1[customer_id],
            "ProductList",
            CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
            )
            VAR ProductSummary =
            SUMMARIZE (
            PerCustomer,
            [ProductList],
            "Customers",
            DISTINCTCOUNT ( Table1[customer_id] )
            )
            VAR Ranked =
            ADDCOLUMNS (
            ProductSummary,
            "Rank",
            RANKX (
            ProductSummary,
            [Customers] + (1 - 1 / RANKX ( ProductSummary, [ProductList] ) )
            )
            )
            VAR CurrID =
            SELECTEDVALUE ( Table1[customer_id] )
            RETURN
            MAXX ( FILTER ( Ranked, [Rank] = CurrID ), [ProductList] )


            What this does is first create a summary table that computes the product list for each customer.



            Then you take that table and summarize over the distinct product lists and counting the number of customers that have each particular combination.



            Then I add a ranking column to the previous table ordering first by the number of customers and tiebreaking using a dictionary order of the product list.



            Finally, I extract the product list from this table where the rank matches the index/rank of the current row.





            You could do a nearly identical measure for the customer count, but here's the measure I used that's a bit simpler and handles 0 values and the total:



            Customers =
            VAR PerCustomer =
            SUMMARIZE (
            ALLSELECTED ( Table1 ),
            Table1[customer_id],
            "ProductList",
            CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
            )
            VAR ProductCombo = [ProductCombo]
            VAR CustomerCount =
            SUMX ( PerCustomer, IF ( [ProductList] = ProductCombo, 1, 0 ) )
            RETURN
            IF (
            ISFILTERED ( Table1[customer_id] ),
            IF ( CustomerCount = 0, BLANK (), CustomerCount ),
            DISTINCTCOUNT ( Table1[customer_id] )
            )


            The result looks like this



            Example Output






            share|improve this answer





















              Your Answer






              StackExchange.ifUsing("editor", function () {
              StackExchange.using("externalEditor", function () {
              StackExchange.using("snippets", function () {
              StackExchange.snippets.init();
              });
              });
              }, "code-snippets");

              StackExchange.ready(function() {
              var channelOptions = {
              tags: "".split(" "),
              id: "1"
              };
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function() {
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled) {
              StackExchange.using("snippets", function() {
              createEditor();
              });
              }
              else {
              createEditor();
              }
              });

              function createEditor() {
              StackExchange.prepareEditor({
              heartbeatType: 'answer',
              autoActivateHeartbeat: false,
              convertImagesToLinks: true,
              noModals: true,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: 10,
              bindNavPrevention: true,
              postfix: "",
              imageUploader: {
              brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
              contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
              allowUrls: true
              },
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              });


              }
              });














              draft saved

              draft discarded


















              StackExchange.ready(
              function () {
              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53282223%2fintersection-of-customer-product-purchase-powerbi%23new-answer', 'question_page');
              }
              );

              Post as a guest















              Required, but never shown

























              3 Answers
              3






              active

              oldest

              votes








              3 Answers
              3






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes









              0














              This is pretty tricky since I can't think of a way to use the values of a dynamically calculated table as a field in a visual. (You can create calculated tables, but those aren't responsive to slicers. You can also create dynamically calculated tables inside of a measure, but measures don't return tables, just single values.)



              The only way I can think of to do this requires creating a table for every possible product combination. However, if you have N products, then this table has 2N rows and that blows up fast.



              Here's a calculated table that will output all the combinations:



              Table2 = 
              VAR N = DISTINCTCOUNT(Table1[product])
              VAR Products = SUMMARIZE(Table1,
              Table1[product],
              "Rank",
              RANKX(ALL(Table1),
              Table1[product],
              MAX(Table1[product]),
              ASC,
              Dense
              )
              )
              VAR Bits = SELECTCOLUMNS(GENERATESERIES(1, N), "Bit", [Value])
              VAR BinaryString =
              ADDCOLUMNS(
              GENERATESERIES(1, 2^N),
              "Binary",
              CONCATENATEX(
              Bits,
              MOD( TRUNC( [Value] / POWER(2, [Bit]-1) ), 2)
              ,,[Bit]
              ,DESC
              )
              )
              RETURN
              ADDCOLUMNS(
              BinaryString,
              "Combination",
              CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), "")
              )


              Then add a calculated column to get the column delimited version:



              Delimited = 
              VAR Length = LEN(Table2[Combination])
              RETURN
              CONCATENATEX(
              GENERATESERIES(1,Length),
              MID(Table2[Combination], [Value], 1),
              ","
              )


              If you put Delimited the Rows section on a matrix visual and the following measure in the Values section:



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              RETURN SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))


              And filter out any 0 customer values, you should get something like this:



              Slicer and Matrix





              So yeah... not a great solution, especially when N gets big, but maybe better than nothing?





              Edit:



              In order to work for longer product names, let's use a delimiter in the Combination concatenation:



               CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), ",")


              (Note the "" to "," change at the end.)



              And then rewrite the Delimited calculated column to remove excess commas.



              Delimited = 
              VAR RemoveMultipleCommas =
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(Table2[Combination], ",,", ","),
              ",,", ","),
              ",,", ","),
              ",,", ",")
              VAR LeftComma = (LEFT(Table2[Combination]) = ",")
              VAR RightComma = (RIGHT(Table2[Combination]) = ",")
              RETURN
              IF(RemoveMultipleCommas <> ",",
              MID(RemoveMultipleCommas,
              1 + LeftComma,
              LEN(RemoveMultipleCommas) - RightComma - LeftComma
              ), "")


              Finally, let's modify the customers measure a bit so it can subtotal.



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              VAR CustomerCount = SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))
              VAR Total = IF(ISFILTERED(Table2[Delimited]), CustomerCount, COUNTROWS(Summary))
              RETURN IF(Total = 0, BLANK(), Total)


              The Total variable gives the total customer count for the total. Note that I've also set zeros to return as blank so that you don't need to filter out zeros (it will automatically hide those rows).



              Matrix and Slicer






              share|improve this answer























              • That works a treat, thank you! For readability, if I wanted to edit it slightly to have the product names rather than a single letter for each product, would this be easy to do? Also, the total in the matrix shows the MAX value and I can't find a way of editing the measure so that it shows the SUM instead?
                – Jonathan Toft
                Nov 14 at 11:15










              • Please see my edit.
                – Alexis Olson
                Nov 14 at 16:21










              • Thank you so much! I just had to add an order expression into the concantenatex in the measure but it now works perfectly!
                – Jonathan Toft
                Nov 15 at 9:12
















              0














              This is pretty tricky since I can't think of a way to use the values of a dynamically calculated table as a field in a visual. (You can create calculated tables, but those aren't responsive to slicers. You can also create dynamically calculated tables inside of a measure, but measures don't return tables, just single values.)



              The only way I can think of to do this requires creating a table for every possible product combination. However, if you have N products, then this table has 2N rows and that blows up fast.



              Here's a calculated table that will output all the combinations:



              Table2 = 
              VAR N = DISTINCTCOUNT(Table1[product])
              VAR Products = SUMMARIZE(Table1,
              Table1[product],
              "Rank",
              RANKX(ALL(Table1),
              Table1[product],
              MAX(Table1[product]),
              ASC,
              Dense
              )
              )
              VAR Bits = SELECTCOLUMNS(GENERATESERIES(1, N), "Bit", [Value])
              VAR BinaryString =
              ADDCOLUMNS(
              GENERATESERIES(1, 2^N),
              "Binary",
              CONCATENATEX(
              Bits,
              MOD( TRUNC( [Value] / POWER(2, [Bit]-1) ), 2)
              ,,[Bit]
              ,DESC
              )
              )
              RETURN
              ADDCOLUMNS(
              BinaryString,
              "Combination",
              CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), "")
              )


              Then add a calculated column to get the column delimited version:



              Delimited = 
              VAR Length = LEN(Table2[Combination])
              RETURN
              CONCATENATEX(
              GENERATESERIES(1,Length),
              MID(Table2[Combination], [Value], 1),
              ","
              )


              If you put Delimited the Rows section on a matrix visual and the following measure in the Values section:



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              RETURN SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))


              And filter out any 0 customer values, you should get something like this:



              Slicer and Matrix





              So yeah... not a great solution, especially when N gets big, but maybe better than nothing?





              Edit:



              In order to work for longer product names, let's use a delimiter in the Combination concatenation:



               CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), ",")


              (Note the "" to "," change at the end.)



              And then rewrite the Delimited calculated column to remove excess commas.



              Delimited = 
              VAR RemoveMultipleCommas =
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(Table2[Combination], ",,", ","),
              ",,", ","),
              ",,", ","),
              ",,", ",")
              VAR LeftComma = (LEFT(Table2[Combination]) = ",")
              VAR RightComma = (RIGHT(Table2[Combination]) = ",")
              RETURN
              IF(RemoveMultipleCommas <> ",",
              MID(RemoveMultipleCommas,
              1 + LeftComma,
              LEN(RemoveMultipleCommas) - RightComma - LeftComma
              ), "")


              Finally, let's modify the customers measure a bit so it can subtotal.



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              VAR CustomerCount = SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))
              VAR Total = IF(ISFILTERED(Table2[Delimited]), CustomerCount, COUNTROWS(Summary))
              RETURN IF(Total = 0, BLANK(), Total)


              The Total variable gives the total customer count for the total. Note that I've also set zeros to return as blank so that you don't need to filter out zeros (it will automatically hide those rows).



              Matrix and Slicer






              share|improve this answer























              • That works a treat, thank you! For readability, if I wanted to edit it slightly to have the product names rather than a single letter for each product, would this be easy to do? Also, the total in the matrix shows the MAX value and I can't find a way of editing the measure so that it shows the SUM instead?
                – Jonathan Toft
                Nov 14 at 11:15










              • Please see my edit.
                – Alexis Olson
                Nov 14 at 16:21










              • Thank you so much! I just had to add an order expression into the concantenatex in the measure but it now works perfectly!
                – Jonathan Toft
                Nov 15 at 9:12














              0












              0








              0






              This is pretty tricky since I can't think of a way to use the values of a dynamically calculated table as a field in a visual. (You can create calculated tables, but those aren't responsive to slicers. You can also create dynamically calculated tables inside of a measure, but measures don't return tables, just single values.)



              The only way I can think of to do this requires creating a table for every possible product combination. However, if you have N products, then this table has 2N rows and that blows up fast.



              Here's a calculated table that will output all the combinations:



              Table2 = 
              VAR N = DISTINCTCOUNT(Table1[product])
              VAR Products = SUMMARIZE(Table1,
              Table1[product],
              "Rank",
              RANKX(ALL(Table1),
              Table1[product],
              MAX(Table1[product]),
              ASC,
              Dense
              )
              )
              VAR Bits = SELECTCOLUMNS(GENERATESERIES(1, N), "Bit", [Value])
              VAR BinaryString =
              ADDCOLUMNS(
              GENERATESERIES(1, 2^N),
              "Binary",
              CONCATENATEX(
              Bits,
              MOD( TRUNC( [Value] / POWER(2, [Bit]-1) ), 2)
              ,,[Bit]
              ,DESC
              )
              )
              RETURN
              ADDCOLUMNS(
              BinaryString,
              "Combination",
              CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), "")
              )


              Then add a calculated column to get the column delimited version:



              Delimited = 
              VAR Length = LEN(Table2[Combination])
              RETURN
              CONCATENATEX(
              GENERATESERIES(1,Length),
              MID(Table2[Combination], [Value], 1),
              ","
              )


              If you put Delimited the Rows section on a matrix visual and the following measure in the Values section:



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              RETURN SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))


              And filter out any 0 customer values, you should get something like this:



              Slicer and Matrix





              So yeah... not a great solution, especially when N gets big, but maybe better than nothing?





              Edit:



              In order to work for longer product names, let's use a delimiter in the Combination concatenation:



               CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), ",")


              (Note the "" to "," change at the end.)



              And then rewrite the Delimited calculated column to remove excess commas.



              Delimited = 
              VAR RemoveMultipleCommas =
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(Table2[Combination], ",,", ","),
              ",,", ","),
              ",,", ","),
              ",,", ",")
              VAR LeftComma = (LEFT(Table2[Combination]) = ",")
              VAR RightComma = (RIGHT(Table2[Combination]) = ",")
              RETURN
              IF(RemoveMultipleCommas <> ",",
              MID(RemoveMultipleCommas,
              1 + LeftComma,
              LEN(RemoveMultipleCommas) - RightComma - LeftComma
              ), "")


              Finally, let's modify the customers measure a bit so it can subtotal.



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              VAR CustomerCount = SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))
              VAR Total = IF(ISFILTERED(Table2[Delimited]), CustomerCount, COUNTROWS(Summary))
              RETURN IF(Total = 0, BLANK(), Total)


              The Total variable gives the total customer count for the total. Note that I've also set zeros to return as blank so that you don't need to filter out zeros (it will automatically hide those rows).



              Matrix and Slicer






              share|improve this answer














              This is pretty tricky since I can't think of a way to use the values of a dynamically calculated table as a field in a visual. (You can create calculated tables, but those aren't responsive to slicers. You can also create dynamically calculated tables inside of a measure, but measures don't return tables, just single values.)



              The only way I can think of to do this requires creating a table for every possible product combination. However, if you have N products, then this table has 2N rows and that blows up fast.



              Here's a calculated table that will output all the combinations:



              Table2 = 
              VAR N = DISTINCTCOUNT(Table1[product])
              VAR Products = SUMMARIZE(Table1,
              Table1[product],
              "Rank",
              RANKX(ALL(Table1),
              Table1[product],
              MAX(Table1[product]),
              ASC,
              Dense
              )
              )
              VAR Bits = SELECTCOLUMNS(GENERATESERIES(1, N), "Bit", [Value])
              VAR BinaryString =
              ADDCOLUMNS(
              GENERATESERIES(1, 2^N),
              "Binary",
              CONCATENATEX(
              Bits,
              MOD( TRUNC( [Value] / POWER(2, [Bit]-1) ), 2)
              ,,[Bit]
              ,DESC
              )
              )
              RETURN
              ADDCOLUMNS(
              BinaryString,
              "Combination",
              CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), "")
              )


              Then add a calculated column to get the column delimited version:



              Delimited = 
              VAR Length = LEN(Table2[Combination])
              RETURN
              CONCATENATEX(
              GENERATESERIES(1,Length),
              MID(Table2[Combination], [Value], 1),
              ","
              )


              If you put Delimited the Rows section on a matrix visual and the following measure in the Values section:



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              RETURN SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))


              And filter out any 0 customer values, you should get something like this:



              Slicer and Matrix





              So yeah... not a great solution, especially when N gets big, but maybe better than nothing?





              Edit:



              In order to work for longer product names, let's use a delimiter in the Combination concatenation:



               CONCATENATEX(Products, IF(MID([Binary],[Rank],1) = "1", [product], ""), ",")


              (Note the "" to "," change at the end.)



              And then rewrite the Delimited calculated column to remove excess commas.



              Delimited = 
              VAR RemoveMultipleCommas =
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(
              SUBSTITUTE(Table2[Combination], ",,", ","),
              ",,", ","),
              ",,", ","),
              ",,", ",")
              VAR LeftComma = (LEFT(Table2[Combination]) = ",")
              VAR RightComma = (RIGHT(Table2[Combination]) = ",")
              RETURN
              IF(RemoveMultipleCommas <> ",",
              MID(RemoveMultipleCommas,
              1 + LeftComma,
              LEN(RemoveMultipleCommas) - RightComma - LeftComma
              ), "")


              Finally, let's modify the customers measure a bit so it can subtotal.



              customers = 
              VAR Summary = SUMMARIZE(Table1,
              Table1[customer_id],
              "ProductList",
              CONCATENATEX(VALUES(Table1[product]), Table1[product], ","))
              VAR CustomerCount = SUMX(Summary, IF([ProductList] = MAX(Table2[Delimited]), 1, 0))
              VAR Total = IF(ISFILTERED(Table2[Delimited]), CustomerCount, COUNTROWS(Summary))
              RETURN IF(Total = 0, BLANK(), Total)


              The Total variable gives the total customer count for the total. Note that I've also set zeros to return as blank so that you don't need to filter out zeros (it will automatically hide those rows).



              Matrix and Slicer







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Nov 14 at 16:20

























              answered Nov 13 at 19:12









              Alexis Olson

              12.4k21633




              12.4k21633












              • That works a treat, thank you! For readability, if I wanted to edit it slightly to have the product names rather than a single letter for each product, would this be easy to do? Also, the total in the matrix shows the MAX value and I can't find a way of editing the measure so that it shows the SUM instead?
                – Jonathan Toft
                Nov 14 at 11:15










              • Please see my edit.
                – Alexis Olson
                Nov 14 at 16:21










              • Thank you so much! I just had to add an order expression into the concantenatex in the measure but it now works perfectly!
                – Jonathan Toft
                Nov 15 at 9:12


















              • That works a treat, thank you! For readability, if I wanted to edit it slightly to have the product names rather than a single letter for each product, would this be easy to do? Also, the total in the matrix shows the MAX value and I can't find a way of editing the measure so that it shows the SUM instead?
                – Jonathan Toft
                Nov 14 at 11:15










              • Please see my edit.
                – Alexis Olson
                Nov 14 at 16:21










              • Thank you so much! I just had to add an order expression into the concantenatex in the measure but it now works perfectly!
                – Jonathan Toft
                Nov 15 at 9:12
















              That works a treat, thank you! For readability, if I wanted to edit it slightly to have the product names rather than a single letter for each product, would this be easy to do? Also, the total in the matrix shows the MAX value and I can't find a way of editing the measure so that it shows the SUM instead?
              – Jonathan Toft
              Nov 14 at 11:15




              That works a treat, thank you! For readability, if I wanted to edit it slightly to have the product names rather than a single letter for each product, would this be easy to do? Also, the total in the matrix shows the MAX value and I can't find a way of editing the measure so that it shows the SUM instead?
              – Jonathan Toft
              Nov 14 at 11:15












              Please see my edit.
              – Alexis Olson
              Nov 14 at 16:21




              Please see my edit.
              – Alexis Olson
              Nov 14 at 16:21












              Thank you so much! I just had to add an order expression into the concantenatex in the measure but it now works perfectly!
              – Jonathan Toft
              Nov 15 at 9:12




              Thank you so much! I just had to add an order expression into the concantenatex in the measure but it now works perfectly!
              – Jonathan Toft
              Nov 15 at 9:12













              0














              You can also try this measure to calculate the result. 

              [Count Of Customers] :=
              VAR var_products_selection_count = DISTINCTCOUNT ( Sales[product] )
              VAR var_customers = VALUES ( Sales[customer_id] )
              VAR var_customers_products_count =
              ADDCOLUMNS(
              var_customers,
              "products_count",
              VAR var_products_count =
              COUNTROWS (
              FILTER (
              CALCULATETABLE ( VALUES ( Sales[product] ) ),
              CONTAINS (
              Sales,
              Sales[product],
              Sales[product]
              )
              )
              )
              RETURN var_products_count
              )
              RETURN
              COUNTROWS (
              FILTER (
              var_customers_products_count,
              [products_count] = var_products_selection_count
              )
              )





              share|improve this answer


























                0














                You can also try this measure to calculate the result. 

                [Count Of Customers] :=
                VAR var_products_selection_count = DISTINCTCOUNT ( Sales[product] )
                VAR var_customers = VALUES ( Sales[customer_id] )
                VAR var_customers_products_count =
                ADDCOLUMNS(
                var_customers,
                "products_count",
                VAR var_products_count =
                COUNTROWS (
                FILTER (
                CALCULATETABLE ( VALUES ( Sales[product] ) ),
                CONTAINS (
                Sales,
                Sales[product],
                Sales[product]
                )
                )
                )
                RETURN var_products_count
                )
                RETURN
                COUNTROWS (
                FILTER (
                var_customers_products_count,
                [products_count] = var_products_selection_count
                )
                )





                share|improve this answer
























                  0












                  0








                  0






                  You can also try this measure to calculate the result. 

                  [Count Of Customers] :=
                  VAR var_products_selection_count = DISTINCTCOUNT ( Sales[product] )
                  VAR var_customers = VALUES ( Sales[customer_id] )
                  VAR var_customers_products_count =
                  ADDCOLUMNS(
                  var_customers,
                  "products_count",
                  VAR var_products_count =
                  COUNTROWS (
                  FILTER (
                  CALCULATETABLE ( VALUES ( Sales[product] ) ),
                  CONTAINS (
                  Sales,
                  Sales[product],
                  Sales[product]
                  )
                  )
                  )
                  RETURN var_products_count
                  )
                  RETURN
                  COUNTROWS (
                  FILTER (
                  var_customers_products_count,
                  [products_count] = var_products_selection_count
                  )
                  )





                  share|improve this answer












                  You can also try this measure to calculate the result. 

                  [Count Of Customers] :=
                  VAR var_products_selection_count = DISTINCTCOUNT ( Sales[product] )
                  VAR var_customers = VALUES ( Sales[customer_id] )
                  VAR var_customers_products_count =
                  ADDCOLUMNS(
                  var_customers,
                  "products_count",
                  VAR var_products_count =
                  COUNTROWS (
                  FILTER (
                  CALCULATETABLE ( VALUES ( Sales[product] ) ),
                  CONTAINS (
                  Sales,
                  Sales[product],
                  Sales[product]
                  )
                  )
                  )
                  RETURN var_products_count
                  )
                  RETURN
                  COUNTROWS (
                  FILTER (
                  var_customers_products_count,
                  [products_count] = var_products_selection_count
                  )
                  )






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 15 at 2:24









                  Marcus

                  1




                  1























                      0














                      I think I've found a better solution/workaround that doesn't require precomputing all possible combinations. The key is to use a rank/index as a base column and then built off of that.



                      Since the customer_id is already nicely indexed starting from 1 with no gaps, in this case, I will use that, but if it weren't, then you'd want to create an index column to use instead. Note that there cannot be more distinct product combinations within a given filter context than there are customers since each customer only has a single combination.



                      For each index/rank we want to find the product combination that is associated with it and the number of customers for that combination.



                      ProductCombo =
                      VAR PerCustomer =
                      SUMMARIZE (
                      ALLSELECTED ( Table1 ),
                      Table1[customer_id],
                      "ProductList",
                      CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                      )
                      VAR ProductSummary =
                      SUMMARIZE (
                      PerCustomer,
                      [ProductList],
                      "Customers",
                      DISTINCTCOUNT ( Table1[customer_id] )
                      )
                      VAR Ranked =
                      ADDCOLUMNS (
                      ProductSummary,
                      "Rank",
                      RANKX (
                      ProductSummary,
                      [Customers] + (1 - 1 / RANKX ( ProductSummary, [ProductList] ) )
                      )
                      )
                      VAR CurrID =
                      SELECTEDVALUE ( Table1[customer_id] )
                      RETURN
                      MAXX ( FILTER ( Ranked, [Rank] = CurrID ), [ProductList] )


                      What this does is first create a summary table that computes the product list for each customer.



                      Then you take that table and summarize over the distinct product lists and counting the number of customers that have each particular combination.



                      Then I add a ranking column to the previous table ordering first by the number of customers and tiebreaking using a dictionary order of the product list.



                      Finally, I extract the product list from this table where the rank matches the index/rank of the current row.





                      You could do a nearly identical measure for the customer count, but here's the measure I used that's a bit simpler and handles 0 values and the total:



                      Customers =
                      VAR PerCustomer =
                      SUMMARIZE (
                      ALLSELECTED ( Table1 ),
                      Table1[customer_id],
                      "ProductList",
                      CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                      )
                      VAR ProductCombo = [ProductCombo]
                      VAR CustomerCount =
                      SUMX ( PerCustomer, IF ( [ProductList] = ProductCombo, 1, 0 ) )
                      RETURN
                      IF (
                      ISFILTERED ( Table1[customer_id] ),
                      IF ( CustomerCount = 0, BLANK (), CustomerCount ),
                      DISTINCTCOUNT ( Table1[customer_id] )
                      )


                      The result looks like this



                      Example Output






                      share|improve this answer


























                        0














                        I think I've found a better solution/workaround that doesn't require precomputing all possible combinations. The key is to use a rank/index as a base column and then built off of that.



                        Since the customer_id is already nicely indexed starting from 1 with no gaps, in this case, I will use that, but if it weren't, then you'd want to create an index column to use instead. Note that there cannot be more distinct product combinations within a given filter context than there are customers since each customer only has a single combination.



                        For each index/rank we want to find the product combination that is associated with it and the number of customers for that combination.



                        ProductCombo =
                        VAR PerCustomer =
                        SUMMARIZE (
                        ALLSELECTED ( Table1 ),
                        Table1[customer_id],
                        "ProductList",
                        CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                        )
                        VAR ProductSummary =
                        SUMMARIZE (
                        PerCustomer,
                        [ProductList],
                        "Customers",
                        DISTINCTCOUNT ( Table1[customer_id] )
                        )
                        VAR Ranked =
                        ADDCOLUMNS (
                        ProductSummary,
                        "Rank",
                        RANKX (
                        ProductSummary,
                        [Customers] + (1 - 1 / RANKX ( ProductSummary, [ProductList] ) )
                        )
                        )
                        VAR CurrID =
                        SELECTEDVALUE ( Table1[customer_id] )
                        RETURN
                        MAXX ( FILTER ( Ranked, [Rank] = CurrID ), [ProductList] )


                        What this does is first create a summary table that computes the product list for each customer.



                        Then you take that table and summarize over the distinct product lists and counting the number of customers that have each particular combination.



                        Then I add a ranking column to the previous table ordering first by the number of customers and tiebreaking using a dictionary order of the product list.



                        Finally, I extract the product list from this table where the rank matches the index/rank of the current row.





                        You could do a nearly identical measure for the customer count, but here's the measure I used that's a bit simpler and handles 0 values and the total:



                        Customers =
                        VAR PerCustomer =
                        SUMMARIZE (
                        ALLSELECTED ( Table1 ),
                        Table1[customer_id],
                        "ProductList",
                        CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                        )
                        VAR ProductCombo = [ProductCombo]
                        VAR CustomerCount =
                        SUMX ( PerCustomer, IF ( [ProductList] = ProductCombo, 1, 0 ) )
                        RETURN
                        IF (
                        ISFILTERED ( Table1[customer_id] ),
                        IF ( CustomerCount = 0, BLANK (), CustomerCount ),
                        DISTINCTCOUNT ( Table1[customer_id] )
                        )


                        The result looks like this



                        Example Output






                        share|improve this answer
























                          0












                          0








                          0






                          I think I've found a better solution/workaround that doesn't require precomputing all possible combinations. The key is to use a rank/index as a base column and then built off of that.



                          Since the customer_id is already nicely indexed starting from 1 with no gaps, in this case, I will use that, but if it weren't, then you'd want to create an index column to use instead. Note that there cannot be more distinct product combinations within a given filter context than there are customers since each customer only has a single combination.



                          For each index/rank we want to find the product combination that is associated with it and the number of customers for that combination.



                          ProductCombo =
                          VAR PerCustomer =
                          SUMMARIZE (
                          ALLSELECTED ( Table1 ),
                          Table1[customer_id],
                          "ProductList",
                          CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                          )
                          VAR ProductSummary =
                          SUMMARIZE (
                          PerCustomer,
                          [ProductList],
                          "Customers",
                          DISTINCTCOUNT ( Table1[customer_id] )
                          )
                          VAR Ranked =
                          ADDCOLUMNS (
                          ProductSummary,
                          "Rank",
                          RANKX (
                          ProductSummary,
                          [Customers] + (1 - 1 / RANKX ( ProductSummary, [ProductList] ) )
                          )
                          )
                          VAR CurrID =
                          SELECTEDVALUE ( Table1[customer_id] )
                          RETURN
                          MAXX ( FILTER ( Ranked, [Rank] = CurrID ), [ProductList] )


                          What this does is first create a summary table that computes the product list for each customer.



                          Then you take that table and summarize over the distinct product lists and counting the number of customers that have each particular combination.



                          Then I add a ranking column to the previous table ordering first by the number of customers and tiebreaking using a dictionary order of the product list.



                          Finally, I extract the product list from this table where the rank matches the index/rank of the current row.





                          You could do a nearly identical measure for the customer count, but here's the measure I used that's a bit simpler and handles 0 values and the total:



                          Customers =
                          VAR PerCustomer =
                          SUMMARIZE (
                          ALLSELECTED ( Table1 ),
                          Table1[customer_id],
                          "ProductList",
                          CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                          )
                          VAR ProductCombo = [ProductCombo]
                          VAR CustomerCount =
                          SUMX ( PerCustomer, IF ( [ProductList] = ProductCombo, 1, 0 ) )
                          RETURN
                          IF (
                          ISFILTERED ( Table1[customer_id] ),
                          IF ( CustomerCount = 0, BLANK (), CustomerCount ),
                          DISTINCTCOUNT ( Table1[customer_id] )
                          )


                          The result looks like this



                          Example Output






                          share|improve this answer












                          I think I've found a better solution/workaround that doesn't require precomputing all possible combinations. The key is to use a rank/index as a base column and then built off of that.



                          Since the customer_id is already nicely indexed starting from 1 with no gaps, in this case, I will use that, but if it weren't, then you'd want to create an index column to use instead. Note that there cannot be more distinct product combinations within a given filter context than there are customers since each customer only has a single combination.



                          For each index/rank we want to find the product combination that is associated with it and the number of customers for that combination.



                          ProductCombo =
                          VAR PerCustomer =
                          SUMMARIZE (
                          ALLSELECTED ( Table1 ),
                          Table1[customer_id],
                          "ProductList",
                          CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                          )
                          VAR ProductSummary =
                          SUMMARIZE (
                          PerCustomer,
                          [ProductList],
                          "Customers",
                          DISTINCTCOUNT ( Table1[customer_id] )
                          )
                          VAR Ranked =
                          ADDCOLUMNS (
                          ProductSummary,
                          "Rank",
                          RANKX (
                          ProductSummary,
                          [Customers] + (1 - 1 / RANKX ( ProductSummary, [ProductList] ) )
                          )
                          )
                          VAR CurrID =
                          SELECTEDVALUE ( Table1[customer_id] )
                          RETURN
                          MAXX ( FILTER ( Ranked, [Rank] = CurrID ), [ProductList] )


                          What this does is first create a summary table that computes the product list for each customer.



                          Then you take that table and summarize over the distinct product lists and counting the number of customers that have each particular combination.



                          Then I add a ranking column to the previous table ordering first by the number of customers and tiebreaking using a dictionary order of the product list.



                          Finally, I extract the product list from this table where the rank matches the index/rank of the current row.





                          You could do a nearly identical measure for the customer count, but here's the measure I used that's a bit simpler and handles 0 values and the total:



                          Customers =
                          VAR PerCustomer =
                          SUMMARIZE (
                          ALLSELECTED ( Table1 ),
                          Table1[customer_id],
                          "ProductList",
                          CONCATENATEX ( VALUES ( Table1[product] ), Table1[product], "," )
                          )
                          VAR ProductCombo = [ProductCombo]
                          VAR CustomerCount =
                          SUMX ( PerCustomer, IF ( [ProductList] = ProductCombo, 1, 0 ) )
                          RETURN
                          IF (
                          ISFILTERED ( Table1[customer_id] ),
                          IF ( CustomerCount = 0, BLANK (), CustomerCount ),
                          DISTINCTCOUNT ( Table1[customer_id] )
                          )


                          The result looks like this



                          Example Output







                          share|improve this answer












                          share|improve this answer



                          share|improve this answer










                          answered yesterday









                          Alexis Olson

                          12.4k21633




                          12.4k21633






























                              draft saved

                              draft discarded




















































                              Thanks for contributing an answer to Stack Overflow!


                              • Please be sure to answer the question. Provide details and share your research!

                              But avoid



                              • Asking for help, clarification, or responding to other answers.

                              • Making statements based on opinion; back them up with references or personal experience.


                              To learn more, see our tips on writing great answers.





                              Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                              Please pay close attention to the following guidance:


                              • Please be sure to answer the question. Provide details and share your research!

                              But avoid



                              • Asking for help, clarification, or responding to other answers.

                              • Making statements based on opinion; back them up with references or personal experience.


                              To learn more, see our tips on writing great answers.




                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function () {
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53282223%2fintersection-of-customer-product-purchase-powerbi%23new-answer', 'question_page');
                              }
                              );

                              Post as a guest















                              Required, but never shown





















































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown

































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown







                              Popular posts from this blog

                              Guess what letter conforming each word

                              Port of Spain

                              Run scheduled task as local user group (not BUILTIN)