docs: public static PlanNodeStatsEstimate subtractSubsetStats(PlanNodeStatsEstimate superset, PlanNodeStatsEstimate subset)
      {
          if (superset.isOutputRowCountUnknown() || subset.isOutputRowCountUnknown()) {
              return PlanNodeStatsEstimate.unknown();
          }
  
          double supersetRowCount = superset.getOutputRowCount();
          double subsetRowCount = subset.getOutputRowCount();
          double outputRowCount = max(supersetRowCount - subsetRowCount, 0);
  
          // everything will be filtered out after applying negation
          if (outputRowCount == 0) {
              return createZeroStats(superset);
          }
  
          PlanNodeStatsEstimate.Builder result = PlanNodeStatsEstimate.builder();
          result.setOutputRowCount(outputRowCount);
  
          superset.getSymbolsWithKnownStatistics().forEach(symbol -> {
              SymbolStatsEstimate supersetSymbolStats = superset.getSymbolStatistics(symbol);
              SymbolStatsEstimate subsetSymbolStats = subset.getSymbolStatistics(symbol);
  
              SymbolStatsEstimate.Builder newSymbolStats = SymbolStatsEstimate.builder();
  
              // for simplicity keep the average row size the same as in the input
              // in most cases the average row size doesn't change after applying filters
              newSymbolStats.setAverageRowSize(supersetSymbolStats.getAverageRowSize());
  
              // nullsCount
              double supersetNullsCount = supersetSymbolStats.getNullsFraction() * supersetRowCount;
              double subsetNullsCount = subsetSymbolStats.getNullsFraction() * subsetRowCount;
              double newNullsCount = max(supersetNullsCount - subsetNullsCount, 0);
              newSymbolStats.setNullsFraction(min(newNullsCount, outputRowCount) / outputRowCount);
  
              // distinctValuesCount
              double supersetDistinctValues = supersetSymbolStats.getDistinctValuesCount();
              double subsetDistinctValues = subsetSymbolStats.getDistinctValuesCount();
              double newDistinctValuesCount;
              if (isNaN(supersetDistinctValues) || isNaN(subsetDistinctValues)) {
                  newDistinctValuesCount = NaN;
              }
              else if (supersetDistinctValues == 0) {
                  newDistinctValuesCount = 0;
              }
              else if (subsetDistinctValues == 0) {
                  newDistinctValuesCount = supersetDistinctValues;
              }
              else {
                  double supersetNonNullsCount = supersetRowCount - supersetNullsCount;
                  double subsetNonNullsCount = subsetRowCount - subsetNullsCount;
                  double supersetValuesPerDistinctValue = supersetNonNullsCount / supersetDistinctValues;
                  double subsetValuesPerDistinctValue = subsetNonNullsCount / subsetDistinctValues;
                  if (supersetValuesPerDistinctValue <= subsetValuesPerDistinctValue) {
                      newDistinctValuesCount = max(supersetDistinctValues - subsetDistinctValues, 0);
                  }
                  else {
                      newDistinctValuesCount = supersetDistinctValues;
                  }
              }
              newSymbolStats.setDistinctValuesCount(newDistinctValuesCount);
  
              // range
              newSymbolStats.setLowValue(supersetSymbolStats.getLowValue());
              newSymbolStats.setHighValue(supersetSymbolStats.getHighValue());
  
              result.addSymbolStatistics(symbol, newSymbolStats.build());
          });
  
          return result.build();
      }