簡體   English   中英

如何通過pandas寫入一個csv的文件,定時在R讀取?

[英]How to write a csv file via pandas and read it in R at regular intervals?

背景

我實驗室中的駕駛模擬器 PC 生成我通過 python socket接收的數據。 數據每 1/60 秒生成一次。 我不斷地將它保存到名為position.csv的 csv 文件中。 我還想閱讀position.csv中的 position.csv 以在shiny應用程序中使用。 我每 0.2 秒讀一次。

問題

當我在 R 中運行shiny應用程序時,python 拋出PermissionError: [Errno 13] Permission denied: 'position.csv'

Python 將數據保存到 csv 文件的腳本:

import socket
import struct
import pandas as pd

UDP_IP = "127.0.0.1"
UDP_PORT = 9000

sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))

while True:
    data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
    fields = struct.unpack_from('=ddd', data)
    print(fields[0],fields[1],fields[2])
    
    
    dict = {'y': fields[0], 'x': fields[1], 'z': fields[2]}
    my_data = pd.DataFrame([dict], columns=dict.keys())
    open("position.csv", "w")
    my_data.to_csv("position.csv", index=False)

R 用於讀取 csv 文件並在應用程序中使用的腳本:

library(shinydashboard)
library(dplyr)
library(ggplot2)
library(shiny)
library(data.table)



# ui----
ui <- dashboardPage(skin = "black", 
  dashboardHeader(title = "Dashboard"),
  dashboardSidebar(
    sidebarMenu(
      menuItem("Maps", tabName = "navigation", icon = icon("compass"))
  )),
  dashboardBody(
    tabItems(
      # First tab content
      tabItem(tabName = "navigation",
              fluidRow(
                tags$style(type="text/css", ".recalculating {opacity: 1.0;}"),
                plotOutput("plot1")
              )
      )
    )
  )
)


# server----
server <- function(input, output, session) {
 
  

  position <- reactivePoll(200, session,
                       # This function returns the time that log_file was last modified
                       checkFunc = function() {
                         if (file.exists("position.csv"))
                           file.info("position.csv")$mtime[1]
                         else
                           ""
                       },
                       # This function returns the content of log_file
                       valueFunc = function() {
                         data.table::fread("position.csv")
                       }
  )
  

  
  
  xl1 <-  reactive({position()$x - 1000})
  xl2 <-  reactive({position()$x + 1000})
  
  yl1 <-  reactive({position()$y - 800})
  yl2 <-  reactive({position()$y + 800})
  
  
  
 output$plot1 <- renderPlot({
    
    
    ggplot() +
      geom_point(data = position(),
                 aes(x, y),
                 color = "red", size = 5) +
      coord_equal(    xlim = c(xl1(), xl2()),
                      ylim = c(yl1(), yl2())) +
      theme_void()
    
  })
  
  
  
  
  cancel.onSessionEnded <- session$onSessionEnded(function() {

    stopApp()
  })
  
  cancel.onSessionEnded()
  

  
  
}

shinyApp(ui, server)

問題

如何成功讀取和寫入position.csv文件?

甚至不看其中的 shiny 部分,每 0.2 秒訪問文件系統以獲取 CSV 文件一定是一個巨大的瓶頸,並且不太可能是考慮到性能的 go 的最佳方式。

您獲得權限被拒絕的最可能原因可能(我尚未測試)是由於文件鎖定,其中 pandas 在寫入文件時暫時鎖定了文件,而 R 試圖過早讀取它。 坦率地說,即使您沒有收到“拒絕”錯誤,嘗試在文件寫入過程中讀取文件也是可行的,這意味着數據不完整。 寫入和讀取事件應該有一些協調,這樣就不會發生這種情況。

一些想法,未經測試(但有相互文件訪問的經驗):

不基於文件系統

一種替代方法是使用某種形式的流式數據機制,例如 Redis。這可以是一個簡單的“主題”(fifo 隊列)或經過更多考慮(並根據您的需要)的Pub/Sub設置。 這樣,pandas 會將其新數據推送到主題或 pubsub 主題,一個(如果是普通主題)或一個或多個(如果是 pubsub)消費者將獲得完整的數據。

好處:

  • 不使用文件系統,所以最大的瓶頸是網絡帶寬,可能比文件系統延遲低得多,並且寫入和讀取始終是原子的(這意味着沒有像您所面臨的邊讀邊寫的問題);
  • 使用發布/訂閱,任何客戶端都可以“延遲”啟動並獲取所有過去的數據(如果需要),而不會影響任何其他消費者。 意識到“另一個消費者”可能只是您監視事物,不一定是專職處理程序。

缺點:

  • 需要 Redis(或 Apache Kafka 或 RabbitMQ 或類似的東西)作為 .network 上某處的服務,越近(拓撲越好)。
  • pandas 和 R 之間的合作架構需要多考慮一些。這將獲得好處。

這實際上在您的開發計算機上使用 Docker 很容易做到:Redis 圖像是免費的並且性能非常好,我經常將它用於類似的目的。 (不需要 Docker,Redis 在沒有它的情況下安裝得很好,交給你了。)

(Python 有redis-py , R 有redux 。)

基於文件系統

如果必須使用基於文件的 go,那么您需要使用一種方法來完全降低邊讀邊寫的風險。 雖然文件寫入不是原子的(這就是你遇到問題的原因),但文件重命名是。 將文件寫入臨時文件(在同一文件系統上,但不在 R 將讀取的位置或名稱),然后,一旦寫入/關閉,重命名它,以便 R 可以看到它。

例如,假設您的約定是使用/some/path/file1234.csv ,其中1234可能會隨着每次寫入而遞增。 (你可能有時間,沒關系。)讓我們限制 R,這樣它只能看到以文字.csv結尾的文件(不難)。 在 pandas 中,寫入/some/path/file1234.csv.temp ,完成后(您在 python 中close() ),將其重命名為/some/path/file1234.csv 文件重命名后,R 應該可以毫無顧慮地閱讀它。

好處:

  • 架構沒有變化,可能是最快的實施和測試。

缺點:

  • 仍然基於文件系統,這意味着復合延遲來自:.network(如果使用.network 文件系統)、OS、HDD 等。

如果您真的很好奇, MailDir是一個我用於相同目的的目錄結構,盡管它使用巨大的 GPFS(類似 NFS)工作,其中文件創建的延遲可能超過 10-15 秒,文件鎖定不受支持(可靠地),如果沒有我上面提到的文件重命名原子性,我會沉沒。 當然你不需要 maildir 結構的“復雜性”(不多,但相對更復雜)來在 pandas 和 R 之間傳遞文件,但是......原子文件重命名的前提具有優先權並且有很多人在...上下功夫。 (Maildir 的擴展性非常好,afaict。我還沒有嘗試弄清楚的唯一一件事是 maildir 中基於文件系統的 pubsub ...)

(Python 和 R 都以原子方式進行文件重命名,不需要非標准模塊/包。)

可能更改為 python(未經測試):

+ import os

  # ...

      dict = {'y': fields[0], 'x': fields[1], 'z': fields[2]}
      my_data = pd.DataFrame([dict], columns=dict.keys())
-     open("position.csv", "w")
-     my_data.to_csv("position.csv", index=False)
+     my_data.to_csv("position.csv.temp", index=False)
+     try:
+       os.remove("position.csv")
+     except:
+       pass
+     os.rename("position.csv.temp", "position.csv")

在重命名之前使用os.remove("position.csv")而不是使用.old可能就足夠了,我還沒有測試過什么最有效。 我不太關心讀取過程,因為在大多數系統上,文件本身(不管文件系統上的索引節點如何)應該允許 R 繼續讀取,即使文件名已被刪除。 同樣,沒有經過很好的測試。

其他注意事項

  • 文件格式:雖然 CSV 是標准且簡單的格式,但您可能需要考慮讀寫速度更快的格式,例如feather python 和 R 都有支持這個的模塊/包。 我沒有這方面的經驗,但也許https://rstudio-pubs-static.s3.amazonaws.com/207316_edcc0ea0a7c04ea5a63833aaea7051fb.html是一個有用的開始。 (還有“鑲木地板”,我也沒有這方面的經驗。)

  • 下采樣:你真的需要每 0.2 秒讀取一次數據嗎? Shiny (R,) 不能保證立即讀取每個文件。 所以無論如何這里都會有一些“傾斜”,我建議將采樣率降低到 1/秒或每 2-3 秒一次。 根據您真正打算如何使用它,我認識到一些用例與此建議不兼容。 交給你了。


Redis 選項:

Redis 使用默認端口 6379 在我的筆記本電腦上運行。我使用的是 docker,所以我以此開頭。 (請注意,如果您有特殊的網絡設置和/或不能使用主機模式監聽端口,這可能需要調整。它應該可以正常工作,但不需要其他配置。)

$ docker run -p "6379:6379" --name some-redis -d redis

在 python 中:

import pandas as pd
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
df1 = pd.DataFrame(data={'col1': [1, 2], 'col2': [3, 4]})
df2 = pd.DataFrame(data={'col1': [11, 12], 'col2': [13, 14]})
r.rpush('carsim', df1.to_json(orient='records'))
r.rpush('carsim', df2.to_json(orient='records'))

在 R 中:

R <- redux::hiredis()
popped <- R$LPOP("carsim")
popped
# [1] "[{\"col1\":1,\"col2\":3},{\"col1\":2,\"col2\":4}]"
jsonlite::fromJSON(popped)
#   col1 col2
# 1    1    3
# 2    2    4
popped <- R$LPOP("carsim")
jsonlite::fromJSON(popped)
#   col1 col2
# 1   11   13
# 2   12   14
popped <- R$LPOP("carsim")
popped
# NULL

適應類似maildir的目錄

這個技巧使用了 maildirs 的前提,並做了一點妥協(只要只有一個文件編寫器,即 car-sim,這應該沒問題)。

在 python 中:

-     my_data.to_csv("position.csv", index=False)
+     filename = '{:.3f}.csv'.format(time.time())
+     my_data.to_csv('tmp/' + filename, index=False)
+     os.rename('tmp/' + filename, 'new/' + filename)

在 R 中:

files <- list.files("new/", full.names = TRUE)
dat <- rbindlist(lapply(files, fread))
file.rename(files, "cur/")

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM