Скорость вопроса: Создание серии с помощью Deedle / Получение уникальных значений в F # - PullRequest
3 голосов
/ 18 мая 2019

Я использовал решение, предложенное Томасом Петричеком в этом вопросе: Количество уникальных в серии Deedle

Я провел быстрый тест Python Python и решение выше.Я немного изменил функцию, предложенную Томасом, для сортировки счетчиков в обратном порядке, чтобы соответствовать выводу Series.count_values ​​() из функции Python.

let unique s = 
    s |> Series.groupInto (fun _ v -> v) (fun _ g -> Stats.count g)
      |> Series.sortBy (fun v -> -v)

Когда я выполняю следующий код в F #Интерактивный

let rand = Random()
let l = [|1..1_000_000|] |> Array.map (fun _ -> rand.Next(1000))
                         |> Series.ofValues
                         |> unique

И, используя "#time", я получаю в среднем около 1500 мс (150 мс только для создания случайной серии).

Я также протестировал подобный код (вPython консоль PyCharm с использованием Python 3.7)

import time
import pandas
start = time.time()
df = pandas.DataFrame(np.random.randint(0,1000,size=(1000000, 1)), columns=list('A'))
a = df['A'].value_counts()
print(a)
print("ms: %", 1000*(time.time()-start))

И я получаю, для создания DataFrame + value_counts () около 40 мс (половина половина для каждого шага).

Любой совет накак, по крайней мере, закрепить создание серии в F #?Мой код может быть не самым эффективным, и я хотел бы знать, что я мог сделать.Я пытаюсь сменить настроение в своей команде, чтобы переключить некоторые исследования с Python на F #, и я не хочу слышать от них, что F # является способом замедления.Спасибо всем!

1 Ответ

2 голосов
/ 18 мая 2019

Я не знаю ни панд, ни deedle, но я подозреваю, что обобщенная агрегация в Deedle не поспевает за потенциально специализированной версией в pandas (или панды могут быть просто оптимизированы более тщательно).

Один из подходов состоит в том, чтобы выполнить подсчет значений перед передачей наблюдений в Deedle.

Например:

let rand = Random ()
let variant () = 
  Array.init 1_000_000 (fun _ -> rand.Next(1000))
  |> Array.groupBy id
  |> Array.map (fun (k, vs) -> (k, vs.Length))
  |> Array.sortBy (fun (_, c) -> -c)
  |> Series.ofObservations

При сравнении исходного кода с вариантом, приведенным выше, я получил следующие цифры.

Original took: 1197 ms with (60, 30, 11) cc
Variant took: 56 ms with (2, 0, 0) cc

Таким образом, вариант массива выглядит значительно быстрее, а также создает меньшее давление ГХ.

Полный пример кода

open System
open System.Diagnostics
open System.Linq

open Deedle

let now =
  let sw = Stopwatch ()
  sw.Start ()
  fun () -> sw.ElapsedMilliseconds

let time a =
  let inline cc i       = GC.CollectionCount i
  GC.Collect (2, GCCollectionMode.Forced)
  GC.WaitForFullGCComplete () |> ignore
  let before            = now ()
  let bcc0, bcc1, bcc2  = cc 0, cc 1, cc 2
  let v                 = a ()
  let acc0, acc1, acc2  = cc 0, cc 1, cc 2
  let after             = now ()
  v, after - before, (acc0 - bcc0, acc1 - bcc1, acc2 - bcc2)

let seed = 982301576

let run () =
  let rand = Random seed
  let original () = 
    [|1..1_000_000|] 
    |> Array.map (fun _ -> rand.Next(1000))
    |> Series.ofValues
    |> Series.groupInto (fun _ v -> v) (fun _ g -> Stats.count g)
    |> Series.sortBy (fun v -> -v)

  let _, ms, cc = time original
  printfn "Original took: %d ms with %A cc" ms cc

  let rand = Random seed
  let variant () = 
    Array.init 1_000_000 (fun _ -> rand.Next(1000))
    |> Array.groupBy id
    |> Array.map (fun (k, vs) -> (k, vs.Length))
    |> Array.sortBy (fun (_, c) -> -c)
    |> Series.ofObservations

  let _, ms, cc = time variant
  printfn "Variant took: %d ms with %A cc" ms cc

[<EntryPoint>]
let main argv =
  run ()
  0
...