Q:

Determining Number of Joint Sessions Per Product Pair

I have this data-frame:
from pyspark.mllib.linalg.distributed import IndexedRow

rows = sc.parallelize([[1, "A"], [1, 'B'] , [1, "A"], [2, 'A'], [2, 'C'] ,[3,'A'], [3, 'B']])

rows_df = rows.toDF(["session_id", "product"])

rows_df.show()

+----------+-------+
|session_id|product|
+----------+-------+
|         1|      A|
|         1|      B|
|         1|      A|
|         2|      A|
|         2|      C|
|         3|      A|
|         3|      B|
+----------+-------+

I want to know how many joint sessions each product pair have together. The same products can be in a session multiple times, but I only want one count per session per product pair. 
Sample Output:
+---------+---------+-----------------+
|product_a|product_b|num_join_sessions|
+---------+---------+-----------------+
|        A|        B|                2|
|        A|        C|                1|
|        B|        A|                2|
|        B|        C|                0|
|        C|        A|                1|
|        C|        B|                0|
+---------+---------+-----------------+

I'm lost on how to implement this in pyspark. 

A:

Getting the joint session count for pairs that have joint sessions is fairly easy. You can achieve this by joining the DataFrame to itself on session_id and filtering out the rows where the products are the same.
Then you group by the product pairs and count the distinct session_ids. 
import pyspark.sql.functions as f

rows_df.alias("l").join(rows_df.alias("r"), on="session_id", how="inner")\
    .where("l.product != r.product")\
    .groupBy(f.col("l.product").alias("product_a"), f.col("r.product").alias("product_b"))\
    .agg(f.countDistinct("session_id").alias("num_join_sessions"))\
    .show()
#+---------+---------+-----------------+
#|product_a|product_b|num_join_sessions|
#+---------+---------+-----------------+
#|        A|        C|                1|
#|        C|        A|                1|
#|        B|        A|                2|
#|        A|        B|                2|
#+---------+---------+-----------------+

(Side note: if want ONLY unique pairs of products, change the != to < in the where function).
The tricky part is that you also want the pairs that don't have joint sessions. This can be done, but it won't be efficient because you will need to get a Cartesian product of every product pairing.
Nevertheless, here is one approach:
Start with the above and RIGHT join in the Cartesian product of the distinct products pairs.
rows_df.alias("l").join(rows_df.alias("r"), on="session_id", how="inner")\
    .where("l.product != r.product")\
    .groupBy(f.col("l.product").alias("product_a"), f.col("r.product").alias("product_b"))\
    .agg(f.countDistinct("session_id").alias("num_join_sessions"))\
    .join(
        rows_df.selectExpr("product AS product_a").distinct().crossJoin(
            rows_df.selectExpr("product AS product_b").distinct()
        ).where("product_a != product_b").alias("pairs"),
        on=["product_a", "product_b"],
        how="right"
    )\
    .fillna(0)\
    .sort("product_a", "product_b")\
    .show()
#+---------+---------+-----------------+
#|product_a|product_b|num_join_sessions|
#+---------+---------+-----------------+
#|        A|        B|                2|
#|        A|        C|                1|
#|        B|        A|                2|
#|        B|        C|                0|
#|        C|        A|                1|
#|        C|        B|                0|
#+---------+---------+-----------------+

Note: the sort is not needed, but I included it to match the order of the desired output.

