[英]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'
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)
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)消費者將獲得完整的數據。
好處:
缺點:
這實際上在您的開發計算機上使用 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 應該可以毫無顧慮地閱讀它。
好處:
缺點:
如果您真的很好奇, 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 使用默認端口 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
這個技巧使用了 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.