2017-06-13 132 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 
... 

但是,请注意,这也将空文件夹计为文件。