If a function doesn't modify a passed data frame, R doesn't actually copy it. It's formally pass-by-value, but the implementation uses a copy-on-write approach, so no copy is made when the function only reads the parameter's values. That at least covers the common case of passing a bunch of data to a function that builds a statistical model.
Of course that doesn't help in the case where you do want the called function to modify the data, but in-place rather than by making a copy.
If you look at the "future directions" section of pqR's version of the "R Internals" manual, you'll see a brief mention of a plan to implement "call by name" parameter passing in the style of Algol 60, which should address this issue. Before that happens, however, pqR will improve the tracking of references to reduce the number of unnecessary copies made when parameters are passed by value, which may be more than you realize in past versions of R.
It was my understanding that R basically already does implement call-by-name - arguments to a function are passed by name and looking up in the calling environment until you first modify them. Is my understand incorrect, or do Algol 60's call-by-name semantics mean something different?
Sort of. That's why it shouldn't be too hard a modification to implement. For a call-by-name argument, you just have to evaluate the "promise" every time, rather than just the first time. (Assignment to a call-by-name argument will be a bit trickier, but not impossible.)
Of course that doesn't help in the case where you do want the called function to modify the data, but in-place rather than by making a copy.