함수 우선 – rfuns로 Python에서 R 코딩

R-Blogger · 블로그·해설 · 2026-05-22

R-Blogger블로그·해설한국어2026-05-22

함수 우선 – rfuns로 Python에서 R 코딩

함수와 관용구 – rfuns 로 파이썬에서 R 쓰기제 이전 글들을 읽어보셨다면 제가 여러 프로그래밍 언어를 사용한다는 것을 아실 겁니다. 어떤 언어는 다른 언어보다 더 좋아하고, 때때로 문제는 특정 언어를 사용하도록 요구합니다. 그럴 때는 머리를 그 언어에 맞게 전환하고, 해당 언어의 특성을 활용하는 관용구를 사용해야 합니다. 하지만 저는 꼭 그렇게 하고 싶지는 않습니다. 최근 몇 년간 R과 Python 사이의 경계가 크게 흐려졌습니다. {reticulate}를 통해 R 코드 안에서 Python을 사용할 수 있게 되었고, RStudio가 Posit로 바뀌면서 강력한 Python 개발 지원을 시작했으며, Positron이라는 다중 언어 IDE와 다중 언어를 지원하는 Quarto가 등장했습니다.가끔은 Python을 직접 사용해야 할 때가 있습니다. 예를 들어 API를 래핑한 SDK가 존재하고, 제가 원하는 엔드포인트를 정확히 알기 전까지는 별도의 R 버전을 만들고 싶지 않기 때문입니다. 이때 R에서 익숙한 함수들을 Python에 그대로 적용하려고 하면, 실제로 Python에는 그런 함수가 존재하지 않을 때가 많습니다.예를 들어 R의 sapply와 같은 패턴을 Python에서는 다른 이름으로 제공됩니다.sapply(c(2, 3, 4, 5), \(x) x ^ 2) ## [1] 4 9 16 25Python에서는 map을 사용할 수 있는데, 이는 결과를 즉시 보여주지 않는 lazy iterator입니다.map(lambda x: x ** 2, [2, 3, 4, 5]) # 결과가 바로 보이지 않음 list(map(lambda x: x ** 2, [2, 3, 4, 5])) # [4, 9, 16, 25]또는 lazy 하지 않은 리스트 컴프리헨션을 사용할 수도 있습니다.[v ** 2 for v in [2, 3, 4, 5]] # [4, 9, 16, 25]두 번째가 바로 찾아야 할 관용구입니다.다른 예로 R에서 table()함수를 사용하면 벡터의 고유값 빈도를 히스토그램 형태로 얻을 수 있습니다.table(c("b", "a", "c", "a", "b", "a")) # a b c # 3 2 1Python에서는 collections.Counter와 sorted를 조합해 비슷한 결과를 얻을 수 있습니다.from collections import Counter sorted(Counter(["b", "a", "c", "a", "b", "a"]).items()) # [('a', 3), ('b', 2), ('c', 1)]Python 사용자들은 이미 이러한 관용구와 패키지를 기억하고 있을 것입니다. 하지만 저는 “그냥 안 하고 싶다”는 질문을 스스로에게 계속 던집니다. 왜 이런 관용구를 감싸는 함수가 없을까요? 만약 있다면 왜 “table”이라고 그냥 부르지 않을까요? 이름이 가장 기억에 남거나 유용하지 않더라도, R 사용자에게는 즉시 인식되는 이름이기 때문입니다(“sapply” 역시 마찬가지입니다).한 가지 가능한 방법은 Python에서 R을 직접 호출하는 것입니다. 이는 가능하지만, 리스트를 반복할 때마다 그렇게 할 필요는 없다고 생각합니다. Python 패키지 인덱스에는 r-functions라는 패키지가 존재하지만, 이는 개별 R 파일을 RScript를 통해 래핑하는 방식입니다. 저는 “R 인터페이스를 갖춘 순수 Python”을 원합니다.Python은 객체지향 언어이면서도 함수가 존재하므로, 간단히 함수를 정의해 볼 수 있습니다.from collections import Counter def table(x): return dict(sorted(Counter(x).items())) table(["b", "a", "c", "a", "b", "a"]) # {'a': 3, 'b': 2, 'c': 1} def sapply(x, func): return [func(v) for v in x] sapply([2, 3, 4, 5], lambda x: x ** 2) # [4, 9, 16, 25]이와 같이 R의 관용구를 Python 함수 인터페이스로 제공하면 더 편리해집니다. 또 다른 예로, R에서 which()는 TRUE 값의 인덱스를 반환합니다.which(c(FALSE, FALSE, TRUE, FALSE, TRUE)) # [1] 3 5Python에서는 다음과 같이 정의할 수 있습니다.def which(x): return [i for i, v in enumerate(x) if v] which([False, False, True, False, True]) # [2, 4] # Python은 0부터 시작합니다.이러한 접근을 얼마나 확장할 수 있을까요? 상당히 멀리까지 갈 수 있습니다. 예를 들어 R은 벡터화된 연산을 지원합니다. R의 nchar(s)는 문자열 길이를 반환하는데, 벡터에 적용하면 각각의 길이를 반환합니다.# R nchar(c("these", "all", "have", "different", "lengths")) # [1] 5 3 4 9 7Python의 len()은 단일 객체의 길이만 반환합니다.len(["these", "all", "have", "different", "lengths"]) # 5따라서 “proper”한 방법은 리스트 컴프리헨션을 사용하는 것입니다.[len(s) for s in ["these", "all", "have", "different", "lengths"]] # [5, 3, 4, 9, 7]하지만 왜 매번 관용구를 사용해야 할까요? 저는 일반 함수에 리스트가 전달될 경우 내부에서 리스트 컴프리헨션을 적용해 벡터화된 동작을 수행하도록 하는 데코레이터를 만들 생각을 했습니다.import functools def make_vec(func): @functools.wraps(func) def wrapper(arg): if isinstance(arg, (list, tuple)): return [func(x) for x in arg] else: return func(arg) return wrapper
원문 URL
전체 글은 원문 페이지에서 이어서 읽을 수 있습니다.
원문에서 전체 글 읽기
작성자
Jonathan Carroll
출처
R-Blogger
플랫폼
R-Blogger
분류
블로그·해설
언어
한국어
발행일
2026-05-22