function groupBy(df, columnNames) {
	  var rowsByGroup = {};
	  var hashes = [];
	  var _iteratorNormalCompletion2 = true;
	  var _didIteratorError2 = false;
	  var _iteratorError2 = undefined;

	  try {
	    for (var _iterator2 = df.toCollection(true)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
	      var row = _step2.value;
	      var hash = row.select.apply(row, _toConsumableArray(columnNames)).hash();

	      if (!rowsByGroup[hash]) {
	        hashes.push(hash);
	        rowsByGroup[hash] = [];
	      }

	      rowsByGroup[hash].push(row);
	    }
	  } catch (err) {
	    _didIteratorError2 = true;
	    _iteratorError2 = err;
	  } finally {
	    try {
	      if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
	        _iterator2.return();
	      }
	    } finally {
	      if (_didIteratorError2) {
	        throw _iteratorError2;
	      }
	    }
	  }

	  var groups = hashes.reduce(function (groups, hash) {
	    var _rowsByGroup$hash$;

	    groups[hash] = {
	      groupKey: (_rowsByGroup$hash$ = rowsByGroup[hash][0]).select.apply(_rowsByGroup$hash$, _toConsumableArray(columnNames)).toDict(),
	      hash: hash,
	      group: new df.constructor(rowsByGroup[hash], df.listColumns())
	    };
	    return groups;
	  }, {});
	  return new GroupedDataFrame(df, columnNames, groups, hashes);
	}
Melt a DataFrame to make it tidy. It's the reverse of GroupedDataFrame.pivot().
@param {String} [variableColumnName='variable'] The column name containing columns.
@param {String} [variableColumnName='value'] The column name containing values.
@returns {DataFrame} The tidy DataFrame.
@example
df.groupBy('carType').melt('kms')