Naive JS implementation of tidyr's gather function
First, I would be concerned about the performance of your three consecutive map operations followed by reduce, particularly as the size of data grows in terms of number of items in array.

Right now, you have to iterate data 4 times to complete all those operations.  While the 3x mapping operation probably does provide some clarity to the operations by breaking them down to simple transformation steps, I would consider creating a single map callback that could complete all steps of transformation on an item in one pass, minimizing repetitive iteration of the array.  Of course you would probably want to run some performance tests for your expected use cases to understand the performance trade-offs here.  It might be that a usage pattern like the following makes sense:

data.map( record =&gt; {
    // mapping step 1
    // mapping step 2
    // mapping step 3
    return mappedRecord;
}




Maybe the usage example you show is not very meaningful, but I am having a hard time understanding function signature for gather().


What is the difference between the two key and value labels parameters and the columns specified passed from columns array parameter?
The signature does not make it clear to me as to which fields in the input data are to be assigned to the Letter and Value properties (for this example) in the output array.
The parameter naming doesn't seem to make sense.  Why is one called key_label and one called value_label when both are used as keys in the output structure?
From looking at this signature, how is one to determine what the logic is that is to be applied in "splitting" apart the input records?




Do the with_fields() and split_record() methods have any value outside the context of the gather() function?  If not, consider nesting them inside gather as "private" functions only in context of gather();



Stylistically:


I don't like your use of snake_case in javascript, as it is common practice in JS world to use camelCase.
Consider sticking to comments before lines of code to they apply, rather than using commments at the end of the line (which I generally avoid, as I feel they make code harder to read).
There are a few cases where variable naming is not that meaningful - without_f, with_f.  Dropping a few characters from a variable is oftentimes not worth the value you get from having clearly understandable variable names.
When you get a more complex or longer return operation on an arrow function, you might consider using bracket syntax and/or line breaks to make the code easier to read.


For example:


.map(([left, right]) =&gt; right
  .map(long_pair =&gt; Object.assign({}, long_pair, left)))



Could become:

.map(([left, right]) =&gt; {
    return right.map(
        long_pair =&gt; Object.assign({}, long_pair, left)
    );
});

// or
.map(
    ([left, right]) =&gt; right.map(
        long_pair =&gt; Object.assign({}, long_pair, left)
    )
 );


To me, this makes it much clearer that there is a nested mapping operation happening here. This is much easier than trying to count/balance opening closing parenthesis in your head as one might have to do when looking at original code that ends with three closing parenthesis in a row.