archivist 1.5

Za tydzień odbędzie się useR. Mam w planach pokazać na nim pakiet archivist (Marcin Kosiński pisał o nim tutaj rok temu). W najnowszej wersji (1.5) doszło kilka ciekawych funkcjonalności. Poniżej opiszę dwie nowe, imho jedne z ciekawszych.

download

Funkcja aread()

Archivist pozwala na przechowywanie obiektów w kolekcjach nazywanych repozytoriami. Repozytoria mogą być lokalne (na dysku stacji roboczej) lub zdalne (np. na GitHubie). Do repozytorium można zapisać dane funkcją saveToRepo(), a z repozytoriów githubowych dane można pobrać funkcją aread(). Argumentem tej funkcji jest opis repozytorium i hash obiektu do pobrania. Przykładowo poniższy wykres w ggplot2 można pobrać poleceniem

library(archivist)
aread("pbiecek/graphGallery/d74472d5b4eee352ba17c5a6f2472c07")

Wynikiem funkcji aread() jest obiekt, który po pobraniu można modyfikować (np. zmienić kolory, dodać tytuł), z którego można wyciągnąć dane.
Nic nie stoi na przeszkodzie, by dla wszystkich wykresów w raporcie, dodawać linki pozwalające na odtworzenie tabel/wykresów/zbiorów danych. Dzięki temu elementy raporty są odtwarzalne i dostępne. Co więcej, hash ma co prawda 32 znaki ale wystarczy podać tylko kilka pierwszych, o ile jednoznacznie identyfikują obiekt.

Powyższy wykres jest w pełni odtwarzalny z obiektu pbiecek/graphGallery/d74472d5b4eee352ba17c5a6f2472c07.

Funkcja ahistory()

W pakiecie archivist jest też operator %a% który zachowuje się tak jak operator %>% z pakietu magrittr, z tą różnicą, że zapamiętuje w repozytorium wszystkie cząstkowe wyniki wraz z informacją jak powstały.
Funkcja ahistory() potrafi wyświetlić historię takiego obiektu wraz z uchwytami/hash’ami tych obiektów

Przykładowy kod tworzący podsumowanie modelu liniowego

library(dplyr)
iris %a%
     filter(Sepal.Length < 6) %a%
     lm(Petal.Length~Species, data=.) %a%
     summary() -> tmp

Mając taki obiekt (lub jego hash md5) możemy teraz odtworzyć historię (wynikiem jest ramka danych z hash’ami i nazwami kolejnych wywołań)

ahistory(tmp)
iris                                  [ff575c261c949d073b2895b05d1097c3]
 filter(Sepal.Length < 6)             [d3696e13d15223c7d0bbccb33cc20a11]
 lm(Petal.Length ~ Species, data = .) [990861c7c27812ee959f10e5f76fe2c3]
 summary()                            [050e41ec3bc40b3004bc6bdd356acae7]

Po co?

Celem jest wzbogacenie raportów, aplikacji, artykułów o uchwyty do odtwarzalnych wyników, dla których dodatkowo można obejrzeć ich historię. Mamy wątpliwość jak jakiś obiekt powstał? Archivist pozwala na pokazanie obiektu wraz z rodowodem.

4 thoughts on “archivist 1.5”

  1. Takich funkcjonalności faktycznie brakowało. Jednak chyba ciężko skopiować bezpośrednio kod z wyniku funkcji ahistory(tmp). Może dodać parametr: ahistory(tmp, only.code = TRUE), dzięki któremu funkcja by tylko zwracała kod bez hashy, co łatwo byłoby kopiować.
    Ewentualnie przed hashami można dodawać znaki komentarza?

  2. @Marcin,
    technicznie funkcja ahistory() zwraca ramkę danych z dwoma kolumnami – instrukcją (call jako string) i hashem md5 wyniku (jako string).
    Jest dla niej przeciążona funkcja print.ahistory, która robi cat() na ekranie, ale można tę ramkę zapisać i wybrać z niej tylko pierwszą kolumnę (z instrukcjami, wtedy nie będzie widać hash’y).

    Niestety, na razie te instrukcje pełnią raczej rolę informacyjną, nie zawsze wystarczą do pełnego odtworzenia obiektu. W przypadku liczb losowych potrzebne by było jeszcze ziarno generatora liczb losowych, ale nawet bez tego trudno wyśledzić wszystkie zmienne widziane przez 'call’ w przestrzeni nazw (a należałoby zapisać cały kontekst uruchomienia).
    Jednak krok po kroku…

    Jest w '%a%’ jedna brzydka sztuczka, która nie daje mi spać. Stary operator `%>%` tworzył nowe środowisko potomne do parent.frame() w którym ewaluowany był call. Przez to jednak każde z łańcuchowych wywołań ewaluowane było w innym środowisku. Niestety efekt uboczny był taki że te same instrukcje produkowały inne artefakty, te bowiem często jako element składowy pamiętają środowisko (a te były różne). Teraz %a% robi ewaluacje w tym samym środowisku, nadrzędnym do środowiska operatora, dzięki temu można śledzić kolejne artefakty (mają te same hashe), ale też modyfikuje się zewnętrzne środowisko.

    1. „Teraz %a% robi ewaluacje w tym samym środowisku, nadrzędnym do środowiska operatora, dzięki temu można śledzić kolejne artefakty (mają te same hashe), ale też modyfikuje się zewnętrzne środowisko.”

      To może %a% powinno na poziomie zewnętrznego środowiska stworzyć swoje środowisko, tak żeby ewentualne zmiany zachodziły tylko w sztucznym, chwilowym środowisku stworzonym tylko na potrzeby ewaluacji łańcucha? Pakiet archivist ma swoje środowisko, w którym są tworzone obiekty na potrzeby obsługi funkcji takich jak setLocalRepo czy loadFromLocalRepo, to może i każda ewaluacja łańcucha mogłaby mieć swoje chwilowe środowisko w zewnętrznym środowisku dla łańcucha (np. .aEvalEnv), bądź nawet w środowisku archivisty (.ArchEnv)?

  3. Ciekawy pomysł ale chyba nie zadziała.
    Jeżeli chodzi o ewaluacje w środowisku archivista, to może w nim nie być symboli, które wykorzystuje call, np.
    y <- 1:10 seq(1:10) %>% t.test(., y)
    w środowisku archivista nie ma symbolu y.

    Tworzenie nowego środowiska w parent.frame() rozwiązuje powyższy problem, ale albo to środowisko będzie kasowane albo nie. Jeżeli nie będzie to podobnie jak teraz %a% zaśmieca parent frame. A jeżeli będzie kasowane to będzie ono inne dla każdego wywołania w chain (żadne z nich nie wie że jest pierwsze).
    iris %>% fun1() %>% fun2()
    środowisko tworzone w fun2 będzie innym niż to tworzone w fun1().

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *