2017-06-13 91 views
4

我已經創建了一個簡單的data.tree通過導入文件夾結構中的文件。現在計算複雜文件夾結構中每個文件夾的文件數量?

if (!require("pacman")) install.packages("pacman") 
pacman::p_load_gh("trinker/pathr") 

library(pathr) 
library(data.tree) 

folder_structure <- pathr::tree(path = "/Users/username/Downloads/top_level/", 
use.data.tree = T, include.files = T) 

,我想對象folder_structure轉換爲data.frame,每個文件夾一行,並指定每個文件夾包含多少文件的列。我怎樣才能做到這一點?

例如,我有這個非常簡單的文件夾結構:

top_level_folder 
    sub_folder_1 
     file1.txt 
    sub_folder_2 
     file2.txt 

回答這個問題會涉及創建輸出看起來像這樣:

Folders    Files 
top_level_folder 0 
sub_folder_1  1 
sub_folder_2  1 

第一列可以簡單地通過生成調用list.dirs("/Users/username/Downloads/top_level/"),但我不知道如何生成第二列。請注意,第二列是非遞歸的,這意味着子文件夾內的文件不計算在內(即top_level_folder包含0文件,即使top_level_folder的子文件夾包含2個文件)。

如果您想了解您的解決方案是否可縮放,請下載Rails代碼庫:https://github.com/rails/rails/archive/master.zip並嘗試使用Rails更復雜的文件結構。

+2

我在哪裏可以得到'pathr'軟件包? – Jimbou

+1

@Jimbou:https://github.com/trinker/pathr – histelheim

+2

您能否澄清'sub_folder_1'是否指向同一個文件夾?即是一個文件夾有兩個文件,還是兩個文件夾---具有相同的名稱,這將無法正常工作---因此應該是'sub_folder_1'和'sub_folder_2'? – Felix

回答

3

list.dirs()提供每子目錄從起始文件夾可到達的向量,從而使處理的第一列你的數據框。很方便。

# Get a vector of all the directories and subdirectories from this folder 
dir <- "." 
xs <- list.dirs(dir, recursive = TRUE) 

list.files()可以告訴我們每一個這些文件夾的內容,但它包含的文件和文件夾。我們只是想要這些文件。爲了得到文件數量,我們需要用謂詞過濾輸出list.files()file.info()可以告訴我們一個給定的文件是否是一個目錄,所以我們從中建立我們的謂詞。

# Helper to check if something is folder or file 
is_dir <- function(x) file.info(x)[["isdir"]] 
is_file <- Negate(is_dir) 

現在,我們解決了如何獲取單個文件夾中的文件數量。求和布爾值返回TRUE個案。

# Count the files in a single folder 
count_files_in_one_dir <- function(dir) { 
    files <- list.files(dir, full.names = TRUE) 
    sum(is_file(files)) 
} 

爲了方便起見,我們將該函數包裝起來以使其在多個文件夾上工作。

# Vectorized version of the above 
count_files_in_dir <- function(dir) { 
    vapply(dir, count_files_in_one_dir, numeric(1), USE.NAMES = FALSE) 
} 

現在我們可以計算這些文件。

df <- tibble::data_frame(
    dir = xs, 
    nfiles = count_files_in_dir(xs)) 

df 
#> # A tibble: 688 x 2 
#>             dir nfiles 
#>            <chr> <dbl> 
#> 1             .  11 
#> 2           ./.github  3 
#> 3          ./actioncable  7 
#> 4         ./actioncable/app  0 
#> 5       ./actioncable/app/assets  0 
#> 6    ./actioncable/app/assets/javascripts  1 
#> 7 ./actioncable/app/assets/javascripts/action_cable  5 
#> 8         ./actioncable/bin  1 
#> 9         ./actioncable/lib  1 
#> 10     ./actioncable/lib/action_cable  8 
#> # ... with 678 more rows 
1

您可以使用dplyr鏈與pathr包中的parse_path()函數。 tree函數基本上只是parse_path的一個包裝,因此它更容易直接使用parse_path。例如。像這樣:

library(pathr) 
library(dplyr) 

fls <- dir("C:/RBuildTools/3.3", recursive = T, full.names = T) %>% 
parse_path() %>% 
index(4) %>% # this is where you indicate the level or "depth" 
      # of the folder of which want subfolder file counts 
data.frame(folders = .) %>% 
group_by(folders) %>% 
tally() %>% 
arrange(n) 

# if you want to get rid of all the files in your starting folder 
# just add a 
# filter(folder > 1) at the end of the dplyr chain 

對於我上述代碼產生以下結果:

> fls 
# A tibble: 12 × 2 
     folders  n 
     <fctr> <int> 
1  COPYING  1 
2 README.txt  1 
3 Rtools.txt  1 
4 unins000.dat  1 
5 unins000.exe  1 
6 VERSION.txt  1 
7   bin 56 
8 mingw_libs 200 
9  texinfo5 356 
10 gcc-4.6.3 3787 
11  mingw_32 13707 
12  mingw_64 14619 
+0

這似乎並沒有爲我工作。我已經更新了答案,以更具體地顯示輸出結果的外觀。用你的腳本,我沒有得到示例文件夾結構所需的任何信息。此外,我不確定「深度」是什麼意思 - 您從哪裏開始計算深度,以及它朝哪個方向發展? – histelheim

+0

例如,如果我在'「/ Users/username/Downloads/top_level /」上調用你的函數,那麼我只需要'Folder = Downloads'和'N = 2'。 – histelheim

+1

啊,我明白了。對不起,我的回答不清楚。從理論上講,考慮到你的最後一個例子,你應該把index(4)改爲'index(5)',因爲你想要在第五個斜線或文件夾之後對所有文件夾進行計數(這就是我的意思是深度。 ,我將重新制定它) – Felix

1
dir.create("top_level_folder") 
dir.create("top_level_folder/sub_folder_1") 
dir.create("top_level_folder/sub_folder_2") 
a <- "hello" 
save(a,file = "top_level_folder/sub_folder_1/file1.txt") 
save(a,file = "top_level_folder/sub_folder_2/file2.txt") 

path <- "top_level_folder" 
files <- list.files(path, recursive=TRUE) 
folders <- sapply(strsplit(files,"/"),function(x){x[length(x)-1]}) 
output <- setNames(as.data.frame(table(unlist(folders))),c("Folders","Files")) 

all_folders <- data.frame(Folders = list.dirs(path,full.names=FALSE,recursive=TRUE),stringsAsFactors=FALSE) 
all_folders$Folders[1] <- strsplit(path,",")[[1]][length(strsplit(path,",")[[1]])] 

output <- merge(all_folders,output,all.x = TRUE) 
output$Files[is.na(output$Files)] <- 0 
output <- output[match(all_folders$Folders,output$Folders),] 

#   Folders Files 
# 3 top_level_folder  0 
# 1  sub_folder_1  1 
# 2  sub_folder_2  1 
+0

它適用於這個有限的例子,但只要我擴展到一個更復雜的文件結構,它就會失敗:'表(文件夾)中的錯誤:所有參數必須具有相同的長度' – histelheim

+0

例如,您可以下載Rails代碼庫並嘗試它:https://github.com/rails/rails/archive/master.zip – histelheim

+1

當我試圖將它放入我的profram文件的完整文件夾時,我得到了同樣的錯誤,但它工作時,我將文件夾更改爲unlist(文件夾),你可以嘗試新的腳本嗎? –

0

list.files返回所有文件和目錄路徑。沒有is.file功能,但有dir.exists。既然我們知道所有的路徑都是實際的節點,那些不是目錄的路徑將被視爲文件。

top_level <- '~/rails-master' 
setwd(top_level) 
subitems <- data.frame(
    path = list.files(
    include.dirs = TRUE, 
    recursive = TRUE 
), 
    stringsAsFactors = FALSE 
) 
subitems$is_file <- !dir.exists(subitems$path) 

對於每一行,如果路徑是一個目錄,那麼它是它自己的目錄路徑。如果路徑是一個文件,那麼它的父目錄就是目錄路徑。然後,只需要根據目錄路徑計算is_file爲真。

subitems$dir_path <- ifelse(
    subitems$is_file, 
    dirname(subitems$path), 
    subitems$path 
) 
file_counts <- tapply(subitems$is_file, subitems$dir_path, sum) 
result <- data.frame(
    Folders = names(file_counts), 
    Files = file_counts 
) 
1

你真正需要做的是做一個目錄列表與list.dirs(默認爲recursive = TRUE)和疊代,發現的list.files長度(默認爲recursive = FALSE),該目錄。 Neatening一個不錯的data.frame,

library(purrr) 

files <- .libPaths()[1] %>% # omit for current directory or supply alternate path 
    list.dirs() %>% 
    map_df(~list(path = .x, 
       files = length(list.files(.x)))) 

files 
#> # A tibble: 4,457 x 2 
#>                   path files 
#>                   <chr> <int> 
#> 1    /Library/Frameworks/R.framework/Versions/3.4/Resources/library 314 
#> 2  /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind  9 
#> 3 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/help  5 
#> 4 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/html  2 
#> 5 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/Meta  6 
#> 6  /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/R  3 
#> 7  /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack 14 
#> 8 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack/help  5 
#> 9 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack/html  2 
#> 10 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/acepack/libs  2 
#> # ... with 4,447 more rows 

或全部,如果你喜歡的基礎上,

files <- do.call(rbind, lapply(list.dirs(.libPaths()[1]), function(path){ 
    data.frame(path = path, 
       files = length(list.files(path)), 
       stringsAsFactors = FALSE) 
})) 

head(files) 
#>                  path files 
#> 1   /Library/Frameworks/R.framework/Versions/3.4/Resources/library 314 
#> 2  /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind  9 
#> 3 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/help  5 
#> 4 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/html  2 
#> 5 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/Meta  6 
#> 6 /Library/Frameworks/R.framework/Versions/3.4/Resources/library/abind/R  3 
1

這裏是一個非常緊湊的解決方案:

print(folder_structure, 
     files = function(node) sum(Get(node$children, 'isLeaf')), 
     filterFun = isNotLeaf, 
     pruneMethod = NULL 
) 

這會產生這樣的事:

             levelName files 
1 data.tree              16 
2 ¦--data              2 
3 ¦--data_gen             2 
4 ¦--.git              8 
5 ¦ ¦--hooks             9 
6 ¦ ¦--info             1 
7 ¦ ¦--logs             1 
8 ¦ ¦ °--refs            1 
9 ¦ ¦  ¦--heads           4 
10 ¦ ¦  ¦--remotes          0 
11 ¦ ¦  ¦ °--origin          5 
12 ¦ ¦--objects            0 
13 ¦ ¦ ¦--01             4 
14 ¦ ¦ ¦--02             5 
... 

但是,請注意,這也將空文件夾計爲文件。

相關問題